1use crate::{
25 core::{
26 append_extension, err, info, io::FileError, ok_or_return, parking_lot::Mutex, warn, Uuid,
27 },
28 io::ResourceIo,
29 loader::ResourceLoadersContainer,
30 metadata::ResourceMetadata,
31};
32use fxhash::FxHashSet;
33use fyrox_core::{futures::executor::block_on, SafeLock};
34use ron::ser::PrettyConfig;
35use std::sync::atomic::{AtomicBool, Ordering};
36use std::{
37 collections::BTreeMap,
38 path::{Path, PathBuf},
39 sync::Arc,
40};
41
42pub type RegistryContainer = BTreeMap<Uuid, PathBuf>;
44
45#[allow(async_fn_in_trait)]
47pub trait RegistryContainerExt: Sized {
48 fn serialize_to_string(&self) -> Result<String, FileError>;
50
51 async fn load_from_file(path: &Path, resource_io: &dyn ResourceIo) -> Result<Self, FileError>;
54
55 async fn save(&self, path: &Path, resource_io: &dyn ResourceIo) -> Result<(), FileError>;
58
59 fn save_sync(&self, path: &Path, resource_io: &dyn ResourceIo) -> Result<(), FileError>;
61}
62
63impl RegistryContainerExt for RegistryContainer {
64 fn serialize_to_string(&self) -> Result<String, FileError> {
65 ron::ser::to_string_pretty(self, PrettyConfig::default()).map_err(|err| {
66 FileError::Custom(format!(
67 "Unable to serialize resource registry! Reason: {err}"
68 ))
69 })
70 }
71
72 async fn load_from_file(path: &Path, resource_io: &dyn ResourceIo) -> Result<Self, FileError> {
73 resource_io.load_file(path).await.and_then(|metadata| {
74 ron::de::from_bytes::<Self>(&metadata).map_err(|err| {
75 FileError::Custom(format!(
76 "Unable to deserialize the resource registry. Reason: {err}"
77 ))
78 })
79 })
80 }
81
82 async fn save(&self, path: &Path, resource_io: &dyn ResourceIo) -> Result<(), FileError> {
83 let string = self.serialize_to_string()?;
84 resource_io.write_file(path, string.into_bytes()).await
85 }
86
87 fn save_sync(&self, path: &Path, resource_io: &dyn ResourceIo) -> Result<(), FileError> {
88 let string = self.serialize_to_string()?;
89 resource_io.write_file_sync(path, string.as_bytes())
90 }
91}
92
93#[derive(Default, Clone)]
95pub struct ResourceRegistryStatusFlag(Arc<AtomicBool>);
96
97impl ResourceRegistryStatusFlag {
98 pub fn is_loaded(&self) -> bool {
100 self.0.load(Ordering::SeqCst)
101 }
102
103 pub fn mark_as_loaded(&self) {
105 self.0.store(true, Ordering::SeqCst);
106 info!("Resource registry finished loading.");
107 }
108
109 pub fn mark_as_unloaded(&self) {
112 self.0.store(false, Ordering::SeqCst);
113 }
114}
115
116pub struct ResourceRegistryRefMut<'a> {
120 registry: &'a mut ResourceRegistry,
121 changed: bool,
122}
123
124pub struct RegistryUpdate<T> {
127 pub changed: bool,
129 pub value: T,
131}
132
133impl ResourceRegistryRefMut<'_> {
134 pub fn read_metadata(
136 &mut self,
137 path: PathBuf,
138 ) -> Result<RegistryUpdate<ResourceMetadata>, FileError> {
139 let value = block_on(ResourceMetadata::load_from_file_async(
140 &append_extension(&path, ResourceMetadata::EXTENSION),
141 &*self.registry.io,
142 ))?;
143 let changed = self.register(value.resource_id, path).changed;
144 Ok(RegistryUpdate { changed, value })
145 }
146 pub fn write_metadata(
149 &mut self,
150 uuid: Uuid,
151 path: PathBuf,
152 ) -> Result<RegistryUpdate<Option<PathBuf>>, FileError> {
153 ResourceMetadata { resource_id: uuid }.save_sync(
154 &append_extension(&path, ResourceMetadata::EXTENSION),
155 &*self.registry.io,
156 )?;
157 Ok(self.register(uuid, path))
158 }
159
160 pub fn remove_metadata(&mut self, path: impl AsRef<Path>) -> Result<(), FileError> {
163 if self.unregister_path(&path).is_some() {
164 let metadata_path = append_extension(path.as_ref(), ResourceMetadata::EXTENSION);
165
166 self.registry.io.delete_file_sync(&metadata_path)?;
167
168 Ok(())
169 } else {
170 Err(FileError::Custom(format!(
171 "The {:?} resource is not registered in the registry!",
172 path.as_ref()
173 )))
174 }
175 }
176
177 pub fn register(&mut self, uuid: Uuid, path: PathBuf) -> RegistryUpdate<Option<PathBuf>> {
179 if path.as_os_str().is_empty() {
180 panic!("Registering empty path.");
181 }
182 use std::collections::btree_map::Entry;
183 match self.registry.paths.entry(uuid) {
184 Entry::Vacant(entry) => {
185 info!("Registered: {uuid} -> {path:?}");
186 self.changed = true;
187 entry.insert(path);
188 RegistryUpdate {
189 changed: true,
190 value: None,
191 }
192 }
193 Entry::Occupied(mut entry) => {
194 let changed = entry.get() != &path;
195 if changed {
196 info!("Registry update: {uuid} -> {path:?}");
197 self.changed = true;
198 }
199 let value = Some(entry.insert(path));
200 RegistryUpdate { changed, value }
201 }
202 }
203 }
204
205 pub fn unregister(&mut self, uuid: Uuid) -> Option<PathBuf> {
207 let former = self.registry.paths.remove(&uuid);
208 if former.is_some() {
209 info!("Registry remove UUID: {uuid} -> {former:?}");
210 self.changed = true;
211 }
212 former
213 }
214
215 pub fn unregister_path(&mut self, path: impl AsRef<Path>) -> Option<Uuid> {
217 let path = path.as_ref();
218 let uuid = self.registry.path_to_uuid(path)?;
219 info!("Registry remove path: {uuid} -> {path:?}");
220 self.changed = true;
221 self.registry.paths.remove(&uuid);
222 Some(uuid)
223 }
224
225 pub fn set_container(&mut self, registry_container: RegistryContainer) {
227 if self.registry.paths == registry_container {
228 return;
229 }
230 let current = &self.registry.paths;
232 for (uuid, path) in ®istry_container {
233 if current.get(uuid) != Some(path) {
234 info!("Resource {path:?} was registered with {uuid} UUID.");
235 }
236 }
237 self.changed = true;
238 self.registry.paths = registry_container;
239 }
240}
241
242impl Drop for ResourceRegistryRefMut<'_> {
243 fn drop(&mut self) {
244 if self.changed {
245 self.registry.save_sync();
246 }
247 }
248}
249
250#[derive(Clone)]
253pub struct ResourceRegistry {
254 path: PathBuf,
256 paths: RegistryContainer,
261 status: ResourceRegistryStatusFlag,
266 io: Arc<dyn ResourceIo>,
267 pub excluded_folders: FxHashSet<PathBuf>,
271}
272
273impl ResourceRegistry {
274 pub const DEFAULT_PATH: &'static str = "data/resources.registry";
277
278 pub fn new(io: Arc<dyn ResourceIo>) -> Self {
280 let mut excluded_folders = FxHashSet::default();
281
282 excluded_folders.insert(PathBuf::from("target"));
284 excluded_folders.insert(PathBuf::from("build"));
286
287 Self {
288 path: PathBuf::from(Self::DEFAULT_PATH),
289 paths: Default::default(),
290 status: Default::default(),
291 io,
292 excluded_folders,
293 }
294 }
295
296 pub fn status_flag(&self) -> ResourceRegistryStatusFlag {
299 self.status.clone()
300 }
301
302 pub fn normalize_path(&self, path: impl AsRef<Path>) -> PathBuf {
310 self.io.canonicalize_path(path.as_ref()).unwrap()
311 }
312
313 pub fn inner(&self) -> &RegistryContainer {
315 &self.paths
316 }
317
318 pub fn set_path(&mut self, path: impl AsRef<Path>) {
320 self.path = path.as_ref().to_owned();
321 }
322
323 pub fn path(&self) -> &Path {
325 &self.path
326 }
327
328 pub fn directory(&self) -> Option<&Path> {
330 self.path.parent()
331 }
332
333 pub async fn save(&self) {
335 if self.io.can_write() {
336 match self.paths.save(&self.path, &*self.io).await {
337 Err(error) => {
338 err!(
339 "Unable to write the resource registry at the {:?} path! Reason: {:?}",
340 self.path,
341 error
342 )
343 }
344 Ok(_) => {
345 info!("The registry was successfully saved to {:?}!", self.path)
346 }
347 }
348 }
349 }
350
351 pub fn exists_sync(&self) -> bool {
353 self.io.exists_sync(&self.path)
354 }
355
356 pub fn save_sync(&self) {
358 #[cfg(not(target_arch = "wasm32"))]
359 if self.io.can_write() {
360 if let Some(folder) = self.path.parent() {
361 if !self.io.exists_sync(folder) {
362 fyrox_core::log::Log::verify(self.io.create_dir_all_sync(folder));
363 }
364 }
365
366 match self.paths.save_sync(&self.path, &*self.io) {
367 Err(error) => {
368 err!(
369 "Unable to write the resource registry at the {} path! Reason: {:?}",
370 self.path.display(),
371 error
372 )
373 }
374 Ok(_) => {
375 info!(
376 "The registry was successfully saved to {}!",
377 self.path.display()
378 )
379 }
380 }
381 }
382 }
383
384 pub fn modify(&mut self) -> ResourceRegistryRefMut<'_> {
386 ResourceRegistryRefMut {
387 changed: false,
388 registry: self,
389 }
390 }
391
392 pub fn uuid_to_path(&self, uuid: Uuid) -> Option<&Path> {
394 self.paths.get(&uuid).map(|path| path.as_path())
395 }
396
397 pub fn uuid_to_path_buf(&self, uuid: Uuid) -> Option<PathBuf> {
399 self.uuid_to_path(uuid).map(|path| path.to_path_buf())
400 }
401
402 pub fn path_to_uuid(&self, path: &Path) -> Option<Uuid> {
404 self.paths
405 .iter()
406 .find_map(|(k, v)| if v == path { Some(*k) } else { None })
407 }
408
409 pub fn is_registered(&self, path: &Path) -> bool {
411 self.path_to_uuid(path).is_some()
412 }
413
414 pub async fn scan(
421 resource_io: Arc<dyn ResourceIo>,
422 loaders: Arc<Mutex<ResourceLoadersContainer>>,
423 root: impl AsRef<Path>,
424 excluded_folders: FxHashSet<PathBuf>,
425 ) -> RegistryContainer {
426 let registry_path = root.as_ref();
427 let registry_folder = registry_path
428 .parent()
429 .map(|path| path.to_path_buf())
430 .unwrap_or_else(|| PathBuf::from("."));
431
432 info!(
433 "Scanning {} folder for supported resources...",
434 registry_folder.display()
435 );
436
437 let mut container = RegistryContainer::default();
438
439 let mut paths_to_visit = ok_or_return!(
440 resource_io.read_directory(®istry_folder).await,
441 container
442 )
443 .collect::<Vec<_>>();
444
445 while let Some(fs_path) = paths_to_visit.pop() {
446 let path = match resource_io.canonicalize_path(&fs_path) {
447 Ok(path) => path,
448 Err(err) => {
449 warn!(
450 "Unable to make relative path from {fs_path:?} path! The resource won't be \
451 included in the registry! Reason: {err:?}",
452 );
453 continue;
454 }
455 };
456
457 if excluded_folders.contains(&path) {
458 continue;
459 }
460
461 if resource_io.is_dir(&path).await {
462 if let Ok(iter) = resource_io.read_directory(&path).await {
464 paths_to_visit.extend(iter);
465 }
466
467 continue;
468 }
469
470 if !loaders.safe_lock().is_supported_resource(&path) {
471 continue;
472 }
473
474 let metadata_path = append_extension(&path, ResourceMetadata::EXTENSION);
475 let metadata =
476 match ResourceMetadata::load_from_file_async(&metadata_path, &*resource_io).await {
477 Ok(metadata) => metadata,
478 Err(err) => {
479 warn!(
480 "Unable to load metadata for {path:?} resource. Reason: {err}.
481 The metadata file will be added/recreated, do **NOT** delete it!
482 Add it to the version control!",
483 );
484 let new_metadata = ResourceMetadata::new_with_random_id();
485 if let Err(err) =
486 new_metadata.save_async(&metadata_path, &*resource_io).await
487 {
488 warn!("Unable to save resource {path:?} metadata. Reason: {err}");
489 }
490 new_metadata
491 }
492 };
493
494 if let Some(former) = container.insert(metadata.resource_id, path.clone()) {
495 warn!("Resource UUID collision between {path:?} and {former:?}");
496 }
497 }
498
499 container
500 }
501}