1#[cfg(feature = "embedded_watcher")]
2mod embedded_watcher;
3
4#[cfg(feature = "embedded_watcher")]
5pub use embedded_watcher::*;
6
7use crate::io::{
8 memory::{Dir, MemoryAssetReader, Value},
9 AssetSource, AssetSourceBuilders,
10};
11use alloc::boxed::Box;
12use bevy_ecs::resource::Resource;
13use std::path::{Path, PathBuf};
14
15#[cfg(feature = "embedded_watcher")]
16use alloc::borrow::ToOwned;
17
18pub const EMBEDDED: &str = "embedded";
21
22#[derive(Resource, Default)]
28pub struct EmbeddedAssetRegistry {
29 dir: Dir,
30 #[cfg(feature = "embedded_watcher")]
31 root_paths: alloc::sync::Arc<
32 parking_lot::RwLock<bevy_platform::collections::HashMap<Box<Path>, PathBuf>>,
33 >,
34}
35
36impl EmbeddedAssetRegistry {
37 #[cfg_attr(
42 not(feature = "embedded_watcher"),
43 expect(
44 unused_variables,
45 reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
46 )
47 )]
48 pub fn insert_asset(&self, full_path: PathBuf, asset_path: &Path, value: impl Into<Value>) {
49 #[cfg(feature = "embedded_watcher")]
50 self.root_paths
51 .write()
52 .insert(full_path.into(), asset_path.to_owned());
53 self.dir.insert_asset(asset_path, value);
54 }
55
56 #[cfg_attr(
61 not(feature = "embedded_watcher"),
62 expect(
63 unused_variables,
64 reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
65 )
66 )]
67 pub fn insert_meta(&self, full_path: &Path, asset_path: &Path, value: impl Into<Value>) {
68 #[cfg(feature = "embedded_watcher")]
69 self.root_paths
70 .write()
71 .insert(full_path.into(), asset_path.to_owned());
72 self.dir.insert_meta(asset_path, value);
73 }
74
75 pub fn remove_asset(&self, full_path: &Path) -> Option<super::memory::Data> {
79 self.dir.remove_asset(full_path)
80 }
81
82 pub fn register_source(&self, sources: &mut AssetSourceBuilders) {
84 let dir = self.dir.clone();
85 let processed_dir = self.dir.clone();
86
87 #[cfg_attr(
88 not(feature = "embedded_watcher"),
89 expect(
90 unused_mut,
91 reason = "Variable is only mutated when `embedded_watcher` feature is enabled."
92 )
93 )]
94 let mut source = AssetSource::build()
95 .with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() }))
96 .with_processed_reader(move || {
97 Box::new(MemoryAssetReader {
98 root: processed_dir.clone(),
99 })
100 })
101 .with_processed_watch_warning(
104 "Consider enabling the `embedded_watcher` cargo feature.",
105 );
106
107 #[cfg(feature = "embedded_watcher")]
108 {
109 let root_paths = self.root_paths.clone();
110 let dir = self.dir.clone();
111 let processed_root_paths = self.root_paths.clone();
112 let processed_dir = self.dir.clone();
113 source = source
114 .with_watcher(move |sender| {
115 Some(Box::new(EmbeddedWatcher::new(
116 dir.clone(),
117 root_paths.clone(),
118 sender,
119 core::time::Duration::from_millis(300),
120 )))
121 })
122 .with_processed_watcher(move |sender| {
123 Some(Box::new(EmbeddedWatcher::new(
124 processed_dir.clone(),
125 processed_root_paths.clone(),
126 sender,
127 core::time::Duration::from_millis(300),
128 )))
129 });
130 }
131 sources.insert(EMBEDDED, source);
132 }
133}
134
135#[macro_export]
141macro_rules! embedded_path {
142 ($path_str: expr) => {{
143 embedded_path!("src", $path_str)
144 }};
145
146 ($source_path: expr, $path_str: expr) => {{
147 let crate_name = module_path!().split(':').next().unwrap();
148 $crate::io::embedded::_embedded_asset_path(
149 crate_name,
150 $source_path.as_ref(),
151 file!().as_ref(),
152 $path_str.as_ref(),
153 )
154 }};
155}
156
157#[doc(hidden)]
165pub fn _embedded_asset_path(
166 crate_name: &str,
167 src_prefix: &Path,
168 file_path: &Path,
169 asset_path: &Path,
170) -> PathBuf {
171 let mut maybe_parent = file_path.parent();
172 let after_src = loop {
173 let Some(parent) = maybe_parent else {
174 panic!("Failed to find src_prefix {src_prefix:?} in {file_path:?}")
175 };
176 if parent.ends_with(src_prefix) {
177 break file_path.strip_prefix(parent).unwrap();
178 }
179 maybe_parent = parent.parent();
180 };
181 let asset_path = after_src.parent().unwrap().join(asset_path);
182 Path::new(crate_name).join(asset_path)
183}
184
185#[macro_export]
253macro_rules! embedded_asset {
254 ($app: ident, $path: expr) => {{
255 $crate::embedded_asset!($app, "src", $path)
256 }};
257
258 ($app: ident, $source_path: expr, $path: expr) => {{
259 let mut embedded = $app
260 .world_mut()
261 .resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();
262 let path = $crate::embedded_path!($source_path, $path);
263 let watched_path = $crate::io::embedded::watched_path(file!(), $path);
264 embedded.insert_asset(watched_path, &path, include_bytes!($path));
265 }};
266}
267
268#[doc(hidden)]
270#[cfg(feature = "embedded_watcher")]
271pub fn watched_path(source_file_path: &'static str, asset_path: &'static str) -> PathBuf {
272 PathBuf::from(source_file_path)
273 .parent()
274 .unwrap()
275 .join(asset_path)
276}
277
278#[doc(hidden)]
280#[cfg(not(feature = "embedded_watcher"))]
281pub fn watched_path(_source_file_path: &'static str, _asset_path: &'static str) -> PathBuf {
282 PathBuf::from("")
283}
284
285#[macro_export]
287macro_rules! load_internal_asset {
288 ($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
289 let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
290 assets.insert($handle.id(), ($loader)(
291 include_str!($path_str),
292 std::path::Path::new(file!())
293 .parent()
294 .unwrap()
295 .join($path_str)
296 .to_string_lossy()
297 ));
298 }};
299 ($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{
301 let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
302 assets.insert($handle.id(), ($loader)(
303 include_str!($path_str),
304 std::path::Path::new(file!())
305 .parent()
306 .unwrap()
307 .join($path_str)
308 .to_string_lossy(),
309 $($param),+
310 ));
311 }};
312}
313
314#[macro_export]
316macro_rules! load_internal_binary_asset {
317 ($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
318 let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
319 assets.insert(
320 $handle.id(),
321 ($loader)(
322 include_bytes!($path_str).as_ref(),
323 std::path::Path::new(file!())
324 .parent()
325 .unwrap()
326 .join($path_str)
327 .to_string_lossy()
328 .into(),
329 ),
330 );
331 }};
332}
333
334#[cfg(test)]
335mod tests {
336 use super::{EmbeddedAssetRegistry, _embedded_asset_path};
337 use std::path::Path;
338
339 #[test]
344 fn embedded_asset_path_from_local_crate() {
345 let asset_path = _embedded_asset_path(
346 "my_crate",
347 "src".as_ref(),
348 "src/foo/plugin.rs".as_ref(),
349 "the/asset.png".as_ref(),
350 );
351 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
352 }
353
354 #[test]
357 fn embedded_asset_path_from_local_crate_blank_src_path_questionable() {
358 let asset_path = _embedded_asset_path(
359 "my_crate",
360 "".as_ref(),
361 "src/foo/some/deep/path/plugin.rs".as_ref(),
362 "the/asset.png".as_ref(),
363 );
364 assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
365 }
366
367 #[test]
368 #[should_panic(expected = "Failed to find src_prefix \"NOT-THERE\" in \"src")]
369 fn embedded_asset_path_from_local_crate_bad_src() {
370 let _asset_path = _embedded_asset_path(
371 "my_crate",
372 "NOT-THERE".as_ref(),
373 "src/foo/plugin.rs".as_ref(),
374 "the/asset.png".as_ref(),
375 );
376 }
377
378 #[test]
379 fn embedded_asset_path_from_local_example_crate() {
380 let asset_path = _embedded_asset_path(
381 "example_name",
382 "examples/foo".as_ref(),
383 "examples/foo/example.rs".as_ref(),
384 "the/asset.png".as_ref(),
385 );
386 assert_eq!(asset_path, Path::new("example_name/the/asset.png"));
387 }
388
389 #[test]
392 fn embedded_asset_path_from_external_crate() {
393 let asset_path = _embedded_asset_path(
394 "my_crate",
395 "src".as_ref(),
396 "/path/to/crate/src/foo/plugin.rs".as_ref(),
397 "the/asset.png".as_ref(),
398 );
399 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
400 }
401
402 #[test]
403 fn embedded_asset_path_from_external_crate_root_src_path() {
404 let asset_path = _embedded_asset_path(
405 "my_crate",
406 "/path/to/crate/src".as_ref(),
407 "/path/to/crate/src/foo/plugin.rs".as_ref(),
408 "the/asset.png".as_ref(),
409 );
410 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
411 }
412
413 #[test]
416 #[should_panic(expected = "Failed to find src_prefix \"////src\" in")]
417 fn embedded_asset_path_from_external_crate_extraneous_beginning_slashes() {
418 let asset_path = _embedded_asset_path(
419 "my_crate",
420 "////src".as_ref(),
421 "/path/to/crate/src/foo/plugin.rs".as_ref(),
422 "the/asset.png".as_ref(),
423 );
424 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
425 }
426
427 #[test]
430 fn embedded_asset_path_from_external_crate_is_ambiguous() {
431 let asset_path = _embedded_asset_path(
432 "my_crate",
433 "src".as_ref(),
434 "/path/to/.cargo/registry/src/crate/src/src/plugin.rs".as_ref(),
435 "the/asset.png".as_ref(),
436 );
437 assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
439 }
440
441 #[test]
442 fn remove_embedded_asset() {
443 let reg = EmbeddedAssetRegistry::default();
444 let path = std::path::PathBuf::from("a/b/asset.png");
445 reg.insert_asset(path.clone(), &path, &[]);
446 assert!(reg.dir.get_asset(&path).is_some());
447 assert!(reg.remove_asset(&path).is_some());
448 assert!(reg.dir.get_asset(&path).is_none());
449 assert!(reg.remove_asset(&path).is_none());
450 }
451}