add LoadBatch to LoadedFolder

This commit is contained in:
shishanyue 2025-06-19 11:42:35 +08:00
parent c29cbcbf35
commit 3808ea9ede
9 changed files with 184 additions and 53 deletions

View File

@ -1782,13 +1782,13 @@ category = "Assets"
wasm = false
[[example]]
name = "folder_filter"
path = "examples/asset/folder_filter.rs"
name = "folder_batch"
path = "examples/asset/folder_batch.rs"
doc-scrape-examples = true
[package.metadata.example.folder_filter]
[package.metadata.example.folder_batch]
name = "Folder Filter"
description = "Load Folder With Filter"
description = "Load Folder With Batch"
category = "Assets"
wasm = false

View File

@ -62,6 +62,8 @@ uuid = { version = "1.13.1", default-features = false, features = [
"v4",
"serde",
] }
glob = "0.3.2"
tracing = { version = "0.1", default-features = false }
[target.'cfg(target_os = "android")'.dependencies]

View File

@ -1,10 +1,66 @@
use std::{path::Path, sync::Arc};
use std::sync::Arc;
use alloc::vec::Vec;
use crate::{Asset, UntypedHandle};
use bevy_reflect::TypePath;
/// This is use for [`AssetServer::load_folderload_folder_with_batch`](crate::prelude::AssetServer::load_folder_with_batch).
#[derive(Debug, Clone, Copy, Default)]
pub enum LoadBatchKind {
#[default]
White, //Allow loading
Black, //Disallow loading
}
impl LoadBatchKind {
pub fn apply(&self, expr: bool) -> bool {
match self {
LoadBatchKind::White => expr,
LoadBatchKind::Black => !expr,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct LoadBatch {
pub paths: Option<Arc<Vec<&'static str>>>,
pub paths_kind: LoadBatchKind,
pub extensions: Option<Arc<Vec<&'static str>>>,
pub extensions_kind: LoadBatchKind,
}
impl LoadBatch {
pub fn new(
paths: Vec<&'static str>,
paths_kind: LoadBatchKind,
extensions: Vec<&'static str>,
extensions_kind: LoadBatchKind,
) -> Self {
Self {
paths: Some(Arc::new(paths)),
paths_kind,
extensions: Some(Arc::new(extensions)),
extensions_kind,
}
}
pub fn paths(paths: Vec<&'static str>, paths_kind: LoadBatchKind) -> Self {
Self {
paths: Some(Arc::new(paths)),
paths_kind,
extensions: None,
..Default::default()
}
}
pub fn extensions(extensions: Vec<&'static str>, extensions_kind: LoadBatchKind) -> Self {
Self {
paths: None,
extensions: Some(Arc::new(extensions)),
extensions_kind,
..Default::default()
}
}
}
/// A "loaded folder" containing handles for all assets stored in a given [`AssetPath`].
///
/// This is produced by [`AssetServer::load_folder`](crate::prelude::AssetServer::load_folder).
@ -15,5 +71,6 @@ pub struct LoadedFolder {
/// The handles of all assets stored in the folder.
#[dependency]
pub handles: Vec<UntypedHandle>,
pub filter: Option<Arc<dyn Fn(&Path, bool) -> bool + Send + Sync + 'static>>,
/// For filtering files that are required or not required.
pub load_batch: Option<LoadBatch>,
}

View File

@ -1618,7 +1618,7 @@ mod tests {
.init_asset::<SubText>()
.register_asset_loader(CoolTextLoader);
let asset_server = app.world().resource::<AssetServer>().clone();
let handle: Handle<LoadedFolder> = asset_server.load_folder("text", None);
let handle: Handle<LoadedFolder> = asset_server.load_folder("text");
gate_opener.open(a_path);
gate_opener.open(b_path);
gate_opener.open(c_path);

View File

@ -2,7 +2,7 @@ mod info;
mod loaders;
use crate::{
folder::LoadedFolder,
folder::{LoadBatch, LoadedFolder},
io::{
AssetReaderError, AssetSource, AssetSourceEvent, AssetSourceId, AssetSources,
AssetWriterError, ErasedAssetReader, MissingAssetSourceError, MissingAssetWriterError,
@ -15,8 +15,8 @@ use crate::{
},
path::AssetPath,
Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets,
DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UnapprovedPathMode,
UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle,
DeserializeMetaError, ErasedLoadedAsset, Handle, LoadBatchKind, LoadedUntypedAsset,
UnapprovedPathMode, UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle,
};
use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec};
use alloc::{
@ -32,6 +32,7 @@ use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll};
use crossbeam_channel::{Receiver, Sender};
use either::Either;
use futures_lite::{FutureExt, StreamExt};
use glob::Pattern;
use info::*;
use loaders::*;
use parking_lot::{RwLock, RwLockWriteGuard};
@ -944,10 +945,38 @@ impl AssetServer {
/// removed, added or moved. This includes files in subdirectories and moving, adding,
/// or removing complete subdirectories.
#[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
pub fn load_folder<'a>(
pub fn load_folder<'a>(&self, path: impl Into<AssetPath<'a>>) -> Handle<LoadedFolder> {
let path = path.into().into_owned();
let (handle, should_load) = self
.data
.infos
.write()
.get_or_create_path_handle::<LoadedFolder>(
path.clone(),
HandleLoadingMode::Request,
None,
);
if !should_load {
return handle;
}
let id = handle.id().untyped();
self.load_folder_internal(id, path, None);
handle
}
/// Loads all assets from the specified folder recursively with batch. The [`LoadedFolder`] asset (when it loads) will
/// contain handles to all assets in the folder. You can wait for all assets to load by checking the [`LoadedFolder`]'s
/// [`RecursiveDependencyLoadState`].
///
/// Loading the same folder multiple times will return the same handle. If the `file_watcher`
/// feature is enabled, [`LoadedFolder`] handles will reload when a file in the folder is
/// removed, added or moved. This includes files in subdirectories and moving, adding,
/// or removing complete subdirectories.
#[must_use = "not using the returned strong handle may result in the unexpected release of the assets"]
pub fn load_folder_with_batch<'a>(
&self,
path: impl Into<AssetPath<'a>>,
filter: Option<Arc<dyn Fn(&Path, bool) -> bool + Send + Sync + 'static>>,
load_batch: LoadBatch,
) -> Handle<LoadedFolder> {
let path = path.into().into_owned();
let (handle, should_load) = self
@ -963,16 +992,14 @@ impl AssetServer {
return handle;
}
let id = handle.id().untyped();
self.load_folder_internal(id, path, filter);
self.load_folder_internal(id, path, Some(load_batch));
handle
}
pub(crate) fn load_folder_internal(
&self,
id: UntypedAssetId,
path: AssetPath,
filter: Option<Arc<dyn Fn(&Path, bool) -> bool + Send + Sync + 'static>>,
load_batch: Option<LoadBatch>,
) {
async fn load_folder<'a>(
source: AssetSourceId<'static>,
@ -980,36 +1007,51 @@ impl AssetServer {
reader: &'a dyn ErasedAssetReader,
server: &'a AssetServer,
handles: &'a mut Vec<UntypedHandle>,
filter: Option<Arc<dyn Fn(&Path, bool) -> bool + Send + Sync + 'static>>,
patterns: &'a Vec<Pattern>,
patterns_kind: LoadBatchKind,
extensions: &'a Option<Arc<Vec<&str>>>,
extensions_kind: LoadBatchKind,
) -> Result<(), AssetLoadError> {
let is_dir = reader.is_directory(path).await?;
if is_dir {
let mut path_stream = reader.read_directory(path.as_ref()).await?;
while let Some(child_path) = path_stream.next().await {
let child_is_dir = reader.is_directory(&child_path).await?;
if let Some(ref filter_fn) = filter {
if !filter_fn(&child_path, child_is_dir) {
continue;
}
}
if child_is_dir {
if reader.is_directory(&child_path).await? {
Box::pin(load_folder(
source.clone(),
&child_path,
reader,
server,
handles,
filter.clone(),
patterns,
patterns_kind,
extensions,
extensions_kind,
))
.await?;
} else {
let path = child_path.to_str().expect("Path should be a valid string.");
if let Some(ref filter_fn) = filter {
if !filter_fn(&child_path, false) {
continue;
}
if !patterns_kind
.apply(patterns.iter().any(|pattern| pattern.matches(path)))
{
continue;
}
if let Some(extensions) = extensions {
match child_path.extension() {
Some(ext) => {
if !extensions_kind.apply(extensions.contains(
&ext.to_str().expect("Path should be a valid string."),
)) {
continue;
}
}
None => break,
};
}
let asset_path = AssetPath::parse(path).with_source(source.clone());
match server.load_untyped_async(asset_path).await {
Ok(handle) => handles.push(handle),
// skip assets that cannot be loaded
@ -1026,6 +1068,32 @@ impl AssetServer {
}
let path = path.into_owned();
let patterns: Vec<Pattern> = load_batch
.as_ref()
.map(|batch| {
batch
.paths
.clone()
.unwrap_or(Arc::new(vec!["*/*"]))
.iter()
.map(|path| Pattern::new(path).expect("Failed to create pattern from path"))
.collect()
})
.unwrap_or(vec![Pattern::new("*/*").unwrap()]);
let patterns_kind = load_batch
.as_ref()
.map(|batch| batch.paths_kind)
.unwrap_or_default();
let extensions = load_batch
.as_ref()
.map(|batch| batch.extensions.clone())
.unwrap_or_default();
let extensions_kind = load_batch
.as_ref()
.map(|batch| batch.extensions_kind)
.unwrap_or_default();
let server = self.clone();
IoTaskPool::get()
.spawn(async move {
@ -1052,12 +1120,11 @@ impl AssetServer {
};
let mut handles = Vec::new();
let filter_clone = filter.clone();
match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles,filter).await {
match load_folder(source.id(), path.path(), asset_reader, &server, &mut handles,&patterns,patterns_kind,&extensions,extensions_kind).await {
Ok(_) => server.send_asset_event(InternalAssetEvent::Loaded {
id,
loaded_asset: LoadedAsset::new_with_dependencies(
LoadedFolder { handles,filter:filter_clone },
LoadedFolder { handles,load_batch },
)
.into(),
}),
@ -1643,12 +1710,16 @@ impl AssetServer {
/// A system that manages internal [`AssetServer`] events, such as finalizing asset loads.
pub fn handle_internal_asset_events(world: &mut World) {
world.resource_scope(|world, server: Mut<AssetServer>| {
let mut folder_filters: HashMap<
AssetPath<'_>,
Option<Arc<dyn Fn(&Path, bool) -> bool + Send + Sync + 'static>>,
> = HashMap::new();
for (id, loaded_folder) in world.get_resource::<Assets<LoadedFolder>>().unwrap().iter() {
folder_filters.insert(server.get_path(id).unwrap(), loaded_folder.filter.clone());
let mut load_batchs: HashMap<AssetPath<'_>, Option<LoadBatch>> = HashMap::new();
for (id, loaded_folder) in world
.get_resource::<Assets<LoadedFolder>>()
.expect("Could not get LoadedFolder Assets")
.iter()
{
load_batchs.insert(
server.get_path(id).expect("Path should be a valid string."),
loaded_folder.load_batch.clone(),
);
}
let mut infos = server.data.infos.write();
let var_name = vec![];
@ -1723,7 +1794,10 @@ pub fn handle_internal_asset_events(world: &mut World) {
server.load_folder_internal(
folder_handle.id(),
parent_asset_path.clone(),
folder_filters.get(&parent_asset_path).unwrap().clone(),
load_batchs
.get(&parent_asset_path)
.expect("parent folder is doesn't loaded")
.clone(),
);
}
}

View File

@ -31,9 +31,7 @@ struct RpgSpriteFolder(Handle<LoadedFolder>);
fn load_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
// Load multiple, individual sprites from a folder
commands.insert_resource(RpgSpriteFolder(
asset_server.load_folder("textures/rpg", None),
));
commands.insert_resource(RpgSpriteFolder(asset_server.load_folder("textures/rpg")));
}
fn check_textures(

View File

@ -254,7 +254,7 @@ Example | Description
[Custom Asset IO](../examples/asset/custom_asset_reader.rs) | Implements a custom AssetReader
[Embedded Asset](../examples/asset/embedded_asset.rs) | Embed an asset in the application binary and load it
[Extra asset source](../examples/asset/extra_source.rs) | Load an asset from a non-standard asset source
[Folder Filter](../examples/asset/folder_filter.rs) | Load Folder With Filter
[Folder Filter](../examples/asset/folder_batch.rs) | Load Folder With Batch
[Hot Reloading of Assets](../examples/asset/hot_asset_reloading.rs) | Demonstrates automatic reloading of assets when modified on disk
[Multi-asset synchronization](../examples/asset/multi_asset_sync.rs) | Demonstrates how to wait for multiple assets to be loaded.
[Repeated texture configuration](../examples/asset/repeated_texture.rs) | How to configure the texture to repeat instead of the default clamp to edges

View File

@ -52,7 +52,7 @@ fn setup(
// to load.
// If you want to keep the assets in the folder alive, make sure you store the returned handle
// somewhere.
let _loaded_folder: Handle<LoadedFolder> = asset_server.load_folder("models/torus", None);
let _loaded_folder: Handle<LoadedFolder> = asset_server.load_folder("models/torus");
// If you want a handle to a specific asset in a loaded folder, the easiest way to get one is to call load.
// It will _not_ be loaded a second time.

View File

@ -1,6 +1,6 @@
//! In this example we generate four texture atlases (sprite sheets) from a folder containing
//! individual sprites.But the folder containing waste files called "waste.txt"
//! So we use filter to ingrone the waste files
//! So we use load_folder_with_batch to ingrone the waste files and textures/rpg_with_waste/tiles/*
//!
//! The texture atlases are generated with different padding and sampling to demonstrate the
//! effect of these settings, and how bleeding issues can be resolved by padding the sprites.
@ -8,9 +8,8 @@
//! Only one padded and one unpadded texture atlas are rendered to the screen.
//! An upscaled sprite from each of the four atlases are rendered to the screen.
use std::sync::Arc;
use bevy::{asset::LoadedFolder, image::ImageSampler, prelude::*};
use bevy_asset::{LoadBatch, LoadBatchKind};
fn main() {
App::new()
@ -34,13 +33,14 @@ struct RpgSpriteFolder(Handle<LoadedFolder>);
fn load_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
// Load multiple, individual sprites from a folder
commands.insert_resource(RpgSpriteFolder(asset_server.load_folder(
commands.insert_resource(RpgSpriteFolder(asset_server.load_folder_with_batch(
"textures/rpg_with_waste",
Some(Arc::new(|path, is_dir| {
let need = is_dir || path.extension().unwrap_or_default() == "png";
info!("is_dir:{} path:{:?} need:{}", is_dir, path, need);
need
})),
LoadBatch::new(
vec!["textures/rpg_with_waste/tiles/*"],
LoadBatchKind::Black,
vec!["png"],
LoadBatchKind::White,
),
)));
}