Skip to content

Commit a6ddbcc

Browse files
committed
Allow adding and removing assets, and allow registering sources after adding the AssetPlugin.
1 parent d7b2e99 commit a6ddbcc

File tree

6 files changed

+217
-153
lines changed

6 files changed

+217
-153
lines changed

crates/bevy_asset/src/io/embedded/mod.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ mod embedded_watcher;
33

44
#[cfg(feature = "embedded_watcher")]
55
pub use embedded_watcher::*;
6+
use tracing::error;
67

78
use crate::io::{
89
memory::{Dir, MemoryAssetReader, Value},
9-
AssetSourceBuilder, AssetSourceBuilders,
10+
AssetSourceBuilder, AssetSources,
1011
};
1112
use crate::AssetServer;
1213
use alloc::boxed::Box;
@@ -19,8 +20,7 @@ use std::path::{Path, PathBuf};
1920
#[cfg(feature = "embedded_watcher")]
2021
use alloc::borrow::ToOwned;
2122

22-
/// The name of the `embedded` [`AssetSource`](crate::io::AssetSource),
23-
/// as stored in the [`AssetSourceBuilders`] resource.
23+
/// The name of the `embedded` [`AssetSource`](crate::io::AssetSource).
2424
pub const EMBEDDED: &str = "embedded";
2525

2626
/// A [`Resource`] that manages "rust source files" in a virtual in memory [`Dir`], which is intended
@@ -83,18 +83,12 @@ impl EmbeddedAssetRegistry {
8383
self.dir.remove_asset(full_path)
8484
}
8585

86-
/// Registers the [`EMBEDDED`] [`AssetSource`](crate::io::AssetSource) with the given [`AssetSourceBuilders`].
87-
pub fn register_source(&self, sources: &mut AssetSourceBuilders) {
86+
/// Registers the [`EMBEDDED`] [`AssetSource`](crate::io::AssetSource) with the given
87+
/// [`AssetSources`].
88+
pub fn register_source(&self, sources: &mut AssetSources) {
8889
let dir = self.dir.clone();
8990
let processed_dir = self.dir.clone();
9091

91-
#[cfg_attr(
92-
not(feature = "embedded_watcher"),
93-
expect(
94-
unused_mut,
95-
reason = "Variable is only mutated when `embedded_watcher` feature is enabled."
96-
)
97-
)]
9892
let mut source =
9993
AssetSourceBuilder::new(move || Box::new(MemoryAssetReader { root: dir.clone() }))
10094
.with_processed_reader(move || {
@@ -132,7 +126,9 @@ impl EmbeddedAssetRegistry {
132126
)))
133127
});
134128
}
135-
sources.insert(EMBEDDED, source);
129+
if let Err(err) = sources.add(EMBEDDED, &mut source) {
130+
error!("Failed to register asset source: {err}");
131+
}
136132
}
137133
}
138134

crates/bevy_asset/src/io/file/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl FileAssetReader {
5252
/// Returns the base path of the assets directory, which is normally the executable's parent
5353
/// directory.
5454
///
55-
/// To change this, set [`AssetPlugin::file_path`][crate::AssetPlugin::file_path].
55+
/// To change this, set [`DefaultAssetSource::FromPaths::file_path`][crate::DefaultAssetSource::FromPaths::file_path].
5656
pub fn get_base_path() -> PathBuf {
5757
get_base_path()
5858
}

crates/bevy_asset/src/io/source.rs

Lines changed: 85 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ use alloc::{
88
sync::Arc,
99
};
1010
use atomicow::CowArc;
11-
use bevy_ecs::resource::Resource;
12-
use bevy_platform::collections::HashMap;
11+
use bevy_platform::collections::{hash_map::Entry, HashMap};
1312
use core::{fmt::Display, hash::Hash, time::Duration};
13+
use std::sync::RwLock;
1414
use thiserror::Error;
1515
use tracing::warn;
1616

@@ -174,7 +174,7 @@ impl AssetSourceBuilder {
174174
watch: bool,
175175
watch_processed: bool,
176176
processing_state: Option<Arc<ProcessingState>>,
177-
) -> AssetSource {
177+
) -> Arc<AssetSource> {
178178
let reader = self.reader.as_mut()();
179179
let writer = self.writer.as_mut().and_then(|w| w(false));
180180
let processed_writer = self.processed_writer.as_mut().and_then(|w| w(true));
@@ -231,7 +231,7 @@ impl AssetSourceBuilder {
231231
source.gate_on_processor(processing_state);
232232
}
233233

234-
source
234+
Arc::new(source)
235235
}
236236

237237
/// Will use the given `reader` function to construct unprocessed [`AssetReader`](crate::io::AssetReader) instances.
@@ -332,84 +332,6 @@ impl AssetSourceBuilder {
332332
}
333333
}
334334

335-
/// A [`Resource`] that hold (repeatable) functions capable of producing new [`AssetReader`](crate::io::AssetReader) and [`AssetWriter`](crate::io::AssetWriter) instances
336-
/// for a given asset source.
337-
#[derive(Resource, Default)]
338-
pub struct AssetSourceBuilders {
339-
sources: HashMap<CowArc<'static, str>, AssetSourceBuilder>,
340-
default: Option<AssetSourceBuilder>,
341-
}
342-
343-
impl AssetSourceBuilders {
344-
/// Inserts a new builder with the given `id`
345-
pub fn insert(&mut self, id: impl Into<AssetSourceId<'static>>, source: AssetSourceBuilder) {
346-
match id.into() {
347-
AssetSourceId::Default => {
348-
self.default = Some(source);
349-
}
350-
AssetSourceId::Name(name) => {
351-
self.sources.insert(name, source);
352-
}
353-
}
354-
}
355-
356-
/// Gets a mutable builder with the given `id`, if it exists.
357-
pub fn get_mut<'a, 'b>(
358-
&'a mut self,
359-
id: impl Into<AssetSourceId<'b>>,
360-
) -> Option<&'a mut AssetSourceBuilder> {
361-
match id.into() {
362-
AssetSourceId::Default => self.default.as_mut(),
363-
AssetSourceId::Name(name) => self.sources.get_mut(&name.into_owned()),
364-
}
365-
}
366-
367-
/// Builds a new [`AssetSources`] collection. If `watch` is true, the unprocessed sources will
368-
/// watch for changes. If `watch_processed` is true, the processed sources will watch for
369-
/// changes. If `processing_state` is [`Some`], the processed readers will be gated on the
370-
/// processing state.
371-
pub(crate) fn build_sources(
372-
&mut self,
373-
watch: bool,
374-
watch_processed: bool,
375-
processing_state: Option<Arc<ProcessingState>>,
376-
) -> AssetSources {
377-
let mut sources = <HashMap<_, _>>::default();
378-
for (id, source) in &mut self.sources {
379-
let source = source.build(
380-
AssetSourceId::Name(id.clone_owned()),
381-
watch,
382-
watch_processed,
383-
processing_state.clone(),
384-
);
385-
sources.insert(id.clone_owned(), Arc::new(source));
386-
}
387-
388-
AssetSources {
389-
sources,
390-
default: self
391-
.default
392-
.as_mut()
393-
.map(|p| {
394-
p.build(
395-
AssetSourceId::Default,
396-
watch,
397-
watch_processed,
398-
processing_state.clone(),
399-
)
400-
})
401-
.map(Arc::new)
402-
.expect(MISSING_DEFAULT_SOURCE),
403-
}
404-
}
405-
406-
/// Initializes the default [`AssetSourceBuilder`] if it has not already been set.
407-
pub fn init_default_source(&mut self, path: &str, processed_path: Option<&str>) {
408-
self.default
409-
.get_or_insert_with(|| AssetSourceBuilder::platform_default(path, processed_path));
410-
}
411-
}
412-
413335
/// A collection of unprocessed and processed [`AssetReader`](crate::io::AssetReader), [`AssetWriter`](crate::io::AssetWriter), and [`AssetWatcher`] instances
414336
/// for a specific asset source, identified by an [`AssetSourceId`].
415337
pub struct AssetSource {
@@ -616,11 +538,44 @@ impl AssetSource {
616538

617539
/// A collection of [`AssetSource`]s.
618540
pub struct AssetSources {
541+
/// The named sources.
619542
sources: HashMap<CowArc<'static, str>, Arc<AssetSource>>,
543+
/// The default source.
620544
default: Arc<AssetSource>,
545+
/// Whether sources should watch the unprocessed assets.
546+
watch: bool,
547+
/// Whether sources should watch the processed assets.
548+
watch_processed: bool,
549+
/// The processing state if these sources are being used with
550+
/// [`AssetProcessor`](crate::AssetProcessor).
551+
///
552+
/// If [`Some`], the processed reader will be gated on this state.
553+
processing_state: Option<Arc<ProcessingState>>,
621554
}
622555

623556
impl AssetSources {
557+
/// Creates a new instance where the default asset source is created from `default`, and sources
558+
/// are built with the other options.
559+
pub(crate) fn new(
560+
default: &mut AssetSourceBuilder,
561+
watch: bool,
562+
watch_processed: bool,
563+
processing_state: Option<Arc<ProcessingState>>,
564+
) -> Arc<RwLock<Self>> {
565+
Arc::new(RwLock::new(Self {
566+
default: default.build(
567+
AssetSourceId::Default,
568+
watch,
569+
watch_processed,
570+
processing_state.clone(),
571+
),
572+
sources: HashMap::new(),
573+
watch,
574+
watch_processed,
575+
processing_state,
576+
}))
577+
}
578+
624579
/// Gets the [`AssetSource`] with the given `id`, if it exists.
625580
pub fn get<'a>(
626581
&self,
@@ -636,6 +591,46 @@ impl AssetSources {
636591
}
637592
}
638593

594+
/// Adds a new named asset source.
595+
///
596+
/// Note: Default asset sources cannot be changed at runtime.
597+
pub fn add(
598+
&mut self,
599+
name: impl Into<CowArc<'static, str>>,
600+
source_builder: &mut AssetSourceBuilder,
601+
) -> Result<(), AddSourceError> {
602+
let name = name.into();
603+
let entry = match self.sources.entry(name) {
604+
Entry::Occupied(entry) => return Err(AddSourceError::NameInUse(entry.key().clone())),
605+
Entry::Vacant(entry) => entry,
606+
};
607+
608+
let name = entry.key().clone();
609+
entry.insert(source_builder.build(
610+
AssetSourceId::Name(name),
611+
self.watch,
612+
self.watch_processed,
613+
self.processing_state.clone(),
614+
));
615+
Ok(())
616+
}
617+
618+
/// Removes an existing named asset source.
619+
///
620+
/// Note: Default asset sources cannot be removed at runtime.
621+
pub fn remove(
622+
&mut self,
623+
name: impl Into<CowArc<'static, str>>,
624+
) -> Result<(), MissingAssetSourceError> {
625+
let name = name.into();
626+
if self.sources.remove(&name).is_none() {
627+
return Err(MissingAssetSourceError(AssetSourceId::Name(
628+
name.clone_owned(),
629+
)));
630+
}
631+
Ok(())
632+
}
633+
639634
/// Iterates all asset sources in the collection (including the default source).
640635
pub fn iter(&self) -> impl Iterator<Item = &Arc<AssetSource>> {
641636
self.sources.values().chain(Some(&self.default))
@@ -655,6 +650,14 @@ impl AssetSources {
655650
}
656651
}
657652

653+
/// An error when attempting to add a new asset source.
654+
#[derive(Error, Debug, Clone, PartialEq, Eq)]
655+
pub enum AddSourceError {
656+
/// An asset source with the given name already exists.
657+
#[error("Asset Source '{0}' already exists")]
658+
NameInUse(CowArc<'static, str>),
659+
}
660+
658661
/// An error returned when an [`AssetSource`] does not exist for a given id.
659662
#[derive(Error, Debug, Clone, PartialEq, Eq)]
660663
#[error("Asset Source '{0}' does not exist")]
@@ -674,6 +677,3 @@ pub struct MissingProcessedAssetReaderError(AssetSourceId<'static>);
674677
#[derive(Error, Debug, Clone)]
675678
#[error("Asset Source '{0}' does not have a processed AssetWriter.")]
676679
pub struct MissingProcessedAssetWriterError(AssetSourceId<'static>);
677-
678-
const MISSING_DEFAULT_SOURCE: &str =
679-
"A default AssetSource is required. Add one to `AssetSourceBuilders`";

0 commit comments

Comments
 (0)