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 bevy_ecs::system::Resource;
12use std::path::{Path, PathBuf};
13
14pub const EMBEDDED: &str = "embedded";
15
16#[derive(Resource, Default)]
22pub struct EmbeddedAssetRegistry {
23 dir: Dir,
24 #[cfg(feature = "embedded_watcher")]
25 root_paths: alloc::sync::Arc<parking_lot::RwLock<bevy_utils::HashMap<Box<Path>, PathBuf>>>,
26}
27
28impl EmbeddedAssetRegistry {
29 #[cfg_attr(
34 not(feature = "embedded_watcher"),
35 expect(
36 unused_variables,
37 reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
38 )
39 )]
40 pub fn insert_asset(&self, full_path: PathBuf, asset_path: &Path, value: impl Into<Value>) {
41 #[cfg(feature = "embedded_watcher")]
42 self.root_paths
43 .write()
44 .insert(full_path.into(), asset_path.to_owned());
45 self.dir.insert_asset(asset_path, value);
46 }
47
48 #[cfg_attr(
53 not(feature = "embedded_watcher"),
54 expect(
55 unused_variables,
56 reason = "The `full_path` argument is not used when `embedded_watcher` is disabled."
57 )
58 )]
59 pub fn insert_meta(&self, full_path: &Path, asset_path: &Path, value: impl Into<Value>) {
60 #[cfg(feature = "embedded_watcher")]
61 self.root_paths
62 .write()
63 .insert(full_path.into(), asset_path.to_owned());
64 self.dir.insert_meta(asset_path, value);
65 }
66
67 pub fn remove_asset(&self, full_path: &Path) -> Option<super::memory::Data> {
71 self.dir.remove_asset(full_path)
72 }
73
74 pub fn register_source(&self, sources: &mut AssetSourceBuilders) {
75 let dir = self.dir.clone();
76 let processed_dir = self.dir.clone();
77
78 #[cfg_attr(
79 not(feature = "embedded_watcher"),
80 expect(
81 unused_mut,
82 reason = "Variable is only mutated when `embedded_watcher` feature is enabled."
83 )
84 )]
85 let mut source = AssetSource::build()
86 .with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() }))
87 .with_processed_reader(move || {
88 Box::new(MemoryAssetReader {
89 root: processed_dir.clone(),
90 })
91 })
92 .with_processed_watch_warning(
95 "Consider enabling the `embedded_watcher` cargo feature.",
96 );
97
98 #[cfg(feature = "embedded_watcher")]
99 {
100 let root_paths = self.root_paths.clone();
101 let dir = self.dir.clone();
102 let processed_root_paths = self.root_paths.clone();
103 let processed_dir = self.dir.clone();
104 source = source
105 .with_watcher(move |sender| {
106 Some(Box::new(EmbeddedWatcher::new(
107 dir.clone(),
108 root_paths.clone(),
109 sender,
110 core::time::Duration::from_millis(300),
111 )))
112 })
113 .with_processed_watcher(move |sender| {
114 Some(Box::new(EmbeddedWatcher::new(
115 processed_dir.clone(),
116 processed_root_paths.clone(),
117 sender,
118 core::time::Duration::from_millis(300),
119 )))
120 });
121 }
122 sources.insert(EMBEDDED, source);
123 }
124}
125
126#[macro_export]
132macro_rules! embedded_path {
133 ($path_str: expr) => {{
134 embedded_path!("src", $path_str)
135 }};
136
137 ($source_path: expr, $path_str: expr) => {{
138 let crate_name = module_path!().split(':').next().unwrap();
139 $crate::io::embedded::_embedded_asset_path(
140 crate_name,
141 $source_path.as_ref(),
142 file!().as_ref(),
143 $path_str.as_ref(),
144 )
145 }};
146}
147
148#[doc(hidden)]
156pub fn _embedded_asset_path(
157 crate_name: &str,
158 src_prefix: &Path,
159 file_path: &Path,
160 asset_path: &Path,
161) -> PathBuf {
162 let mut maybe_parent = file_path.parent();
163 let after_src = loop {
164 let Some(parent) = maybe_parent else {
165 panic!("Failed to find src_prefix {src_prefix:?} in {file_path:?}")
166 };
167 if parent.ends_with(src_prefix) {
168 break file_path.strip_prefix(parent).unwrap();
169 }
170 maybe_parent = parent.parent();
171 };
172 let asset_path = after_src.parent().unwrap().join(asset_path);
173 Path::new(crate_name).join(asset_path)
174}
175
176#[macro_export]
244macro_rules! embedded_asset {
245 ($app: ident, $path: expr) => {{
246 $crate::embedded_asset!($app, "src", $path)
247 }};
248
249 ($app: ident, $source_path: expr, $path: expr) => {{
250 let mut embedded = $app
251 .world_mut()
252 .resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();
253 let path = $crate::embedded_path!($source_path, $path);
254 let watched_path = $crate::io::embedded::watched_path(file!(), $path);
255 embedded.insert_asset(watched_path, &path, include_bytes!($path));
256 }};
257}
258
259#[doc(hidden)]
261#[cfg(feature = "embedded_watcher")]
262pub fn watched_path(source_file_path: &'static str, asset_path: &'static str) -> PathBuf {
263 PathBuf::from(source_file_path)
264 .parent()
265 .unwrap()
266 .join(asset_path)
267}
268
269#[doc(hidden)]
271#[cfg(not(feature = "embedded_watcher"))]
272pub fn watched_path(_source_file_path: &'static str, _asset_path: &'static str) -> PathBuf {
273 PathBuf::from("")
274}
275
276#[macro_export]
278macro_rules! load_internal_asset {
279 ($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
280 let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
281 assets.insert($handle.id(), ($loader)(
282 include_str!($path_str),
283 std::path::Path::new(file!())
284 .parent()
285 .unwrap()
286 .join($path_str)
287 .to_string_lossy()
288 ));
289 }};
290 ($app: ident, $handle: ident, $path_str: expr, $loader: expr $(, $param:expr)+) => {{
292 let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
293 assets.insert($handle.id(), ($loader)(
294 include_str!($path_str),
295 std::path::Path::new(file!())
296 .parent()
297 .unwrap()
298 .join($path_str)
299 .to_string_lossy(),
300 $($param),+
301 ));
302 }};
303}
304
305#[macro_export]
307macro_rules! load_internal_binary_asset {
308 ($app: ident, $handle: expr, $path_str: expr, $loader: expr) => {{
309 let mut assets = $app.world_mut().resource_mut::<$crate::Assets<_>>();
310 assets.insert(
311 $handle.id(),
312 ($loader)(
313 include_bytes!($path_str).as_ref(),
314 std::path::Path::new(file!())
315 .parent()
316 .unwrap()
317 .join($path_str)
318 .to_string_lossy()
319 .into(),
320 ),
321 );
322 }};
323}
324
325#[cfg(test)]
326mod tests {
327 use super::{EmbeddedAssetRegistry, _embedded_asset_path};
328 use std::path::Path;
329
330 #[test]
335 fn embedded_asset_path_from_local_crate() {
336 let asset_path = _embedded_asset_path(
337 "my_crate",
338 "src".as_ref(),
339 "src/foo/plugin.rs".as_ref(),
340 "the/asset.png".as_ref(),
341 );
342 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
343 }
344
345 #[test]
348 fn embedded_asset_path_from_local_crate_blank_src_path_questionable() {
349 let asset_path = _embedded_asset_path(
350 "my_crate",
351 "".as_ref(),
352 "src/foo/some/deep/path/plugin.rs".as_ref(),
353 "the/asset.png".as_ref(),
354 );
355 assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
356 }
357
358 #[test]
359 #[should_panic(expected = "Failed to find src_prefix \"NOT-THERE\" in \"src")]
360 fn embedded_asset_path_from_local_crate_bad_src() {
361 let _asset_path = _embedded_asset_path(
362 "my_crate",
363 "NOT-THERE".as_ref(),
364 "src/foo/plugin.rs".as_ref(),
365 "the/asset.png".as_ref(),
366 );
367 }
368
369 #[test]
370 fn embedded_asset_path_from_local_example_crate() {
371 let asset_path = _embedded_asset_path(
372 "example_name",
373 "examples/foo".as_ref(),
374 "examples/foo/example.rs".as_ref(),
375 "the/asset.png".as_ref(),
376 );
377 assert_eq!(asset_path, Path::new("example_name/the/asset.png"));
378 }
379
380 #[test]
383 fn embedded_asset_path_from_external_crate() {
384 let asset_path = _embedded_asset_path(
385 "my_crate",
386 "src".as_ref(),
387 "/path/to/crate/src/foo/plugin.rs".as_ref(),
388 "the/asset.png".as_ref(),
389 );
390 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
391 }
392
393 #[test]
394 fn embedded_asset_path_from_external_crate_root_src_path() {
395 let asset_path = _embedded_asset_path(
396 "my_crate",
397 "/path/to/crate/src".as_ref(),
398 "/path/to/crate/src/foo/plugin.rs".as_ref(),
399 "the/asset.png".as_ref(),
400 );
401 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
402 }
403
404 #[test]
407 #[should_panic(expected = "Failed to find src_prefix \"////src\" in")]
408 fn embedded_asset_path_from_external_crate_extraneous_beginning_slashes() {
409 let asset_path = _embedded_asset_path(
410 "my_crate",
411 "////src".as_ref(),
412 "/path/to/crate/src/foo/plugin.rs".as_ref(),
413 "the/asset.png".as_ref(),
414 );
415 assert_eq!(asset_path, Path::new("my_crate/foo/the/asset.png"));
416 }
417
418 #[test]
421 fn embedded_asset_path_from_external_crate_is_ambiguous() {
422 let asset_path = _embedded_asset_path(
423 "my_crate",
424 "src".as_ref(),
425 "/path/to/.cargo/registry/src/crate/src/src/plugin.rs".as_ref(),
426 "the/asset.png".as_ref(),
427 );
428 assert_eq!(asset_path, Path::new("my_crate/the/asset.png"));
430 }
431
432 #[test]
433 fn remove_embedded_asset() {
434 let reg = EmbeddedAssetRegistry::default();
435 let path = std::path::PathBuf::from("a/b/asset.png");
436 reg.insert_asset(path.clone(), &path, &[]);
437 assert!(reg.dir.get_asset(&path).is_some());
438 assert!(reg.remove_asset(&path).is_some());
439 assert!(reg.dir.get_asset(&path).is_none());
440 assert!(reg.remove_asset(&path).is_none());
441 }
442}