Skip to content

Commit 956d9cc

Browse files
esensarKanabenkimockersf
authored
Add directory related functions to AndroidAssetReader (#11495)
# Objective - Fixes #9968 ## Solution - Uses [open_dir](https://docs.rs/ndk/latest/ndk/asset/struct.AssetManager.html#method.open_dir) to read directories and collects child list, since it can't be shared across threads. - For `is_directory`, uses result of [open](https://docs.rs/ndk/latest/ndk/asset/struct.AssetManager.html#method.open), which will fail for directories. I tried using the result of `open_dir` for this, but it was successful for files too, which made loading folders return empty lists, since `open_dir` was successful and treated all files as empty directories. - Ignoring `meta` files was copied from filesystem implementation --- ## Changelog - Fixed: Android's AssetReader implementation now supports read_directory and is_directory. ## Notes I noticed late that there was the #9968 issue (I only noticed #9591), so I have also missed that a PR was already open (#9969). Feel free to copy over the fixes from this one over there. The only difference I notice between these 2, is that I have used `open` instead of `open_dir` for `is_directory` implementation. I have tried with `open_dir` too, but unfortunately that didn't work. I tested this on an actual device, using the mobile example, by making some minor changes: ```rust #[derive(Resource)] struct FolderAssets(Handle<LoadedFolder>); // the `bevy_main` proc_macro generates the required boilerplate for iOS and Android #[bevy_main] fn main() { // ... .add_systems(Startup, (setup_scene, load_music_files)) .add_systems( Update, // Removed the handle_lifetime since AudioBundle is added later (touch_camera, button_handler, setup_music), ); // ... } fn load_music_files(asset_server: Res<AssetServer>, mut commands: Commands) { let sounds = asset_server.load_folder("sounds"); commands.insert_resource(FolderAssets(sounds)); } fn setup_music( mut commands: Commands, folders: Res<Assets<LoadedFolder>>, mut loaded_event: EventReader<AssetEvent<LoadedFolder>>, ) { for event in loaded_event.read() { if let AssetEvent::LoadedWithDependencies { id } = event { if let Some(folder) = folders.get(*id) { warn!("Folder items: {:?}", folder.handles); if let Some(source) = folder.handles.first() { commands.spawn(AudioBundle { source: source.clone().typed::<AudioSource>(), settings: PlaybackSettings::LOOP, }); } } } } } ``` --------- Co-authored-by: Kanabenki <lucien.menassol@gmail.com> Co-authored-by: François Mockers <francois.mockers@vleue.com>
1 parent f08f077 commit 956d9cc

1 file changed

Lines changed: 51 additions & 14 deletions

File tree

crates/bevy_asset/src/io/android.rs

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
use crate::io::{
2-
get_meta_path, AssetReader, AssetReaderError, EmptyPathStream, PathStream, Reader, VecReader,
3-
};
4-
use alloc::ffi::CString;
1+
use crate::io::{get_meta_path, AssetReader, AssetReaderError, PathStream, Reader, VecReader};
52
use bevy_utils::tracing::error;
6-
use std::path::Path;
3+
use futures_lite::stream;
4+
use std::{ffi::CString, path::Path};
75

86
/// [`AssetReader`] implementation for Android devices, built on top of Android's [`AssetManager`].
97
///
108
/// Implementation details:
119
///
12-
/// - [`load_path`](AssetIo::load_path) uses the [`AssetManager`] to load files.
13-
/// - [`read_directory`](AssetIo::read_directory) always returns an empty iterator.
10+
/// - All functions use the [`AssetManager`] to load files.
11+
/// - [`is_directory`](AssetReader::is_directory) tries to open the path
12+
/// as a normal file and treats an error as if the path is a directory.
1413
/// - Watching for changes is not supported. The watcher method will do nothing.
1514
///
1615
/// [AssetManager]: https://developer.android.com/reference/android/content/res/AssetManager
@@ -46,15 +45,53 @@ impl AssetReader for AndroidAssetReader {
4645

4746
async fn read_directory<'a>(
4847
&'a self,
49-
_path: &'a Path,
48+
path: &'a Path,
5049
) -> Result<Box<PathStream>, AssetReaderError> {
51-
let stream: Box<PathStream> = Box::new(EmptyPathStream);
52-
error!("Reading directories is not supported with the AndroidAssetReader");
53-
Ok(stream)
50+
let asset_manager = bevy_winit::ANDROID_APP
51+
.get()
52+
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
53+
.asset_manager();
54+
let opened_assets_dir = asset_manager
55+
.open_dir(&CString::new(path.to_str().unwrap()).unwrap())
56+
.ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
57+
58+
let mapped_stream = opened_assets_dir
59+
.filter_map(move |f| {
60+
let file_path = path.join(Path::new(f.to_str().unwrap()));
61+
// filter out meta files as they are not considered assets
62+
if let Some(ext) = file_path.extension().and_then(|e| e.to_str()) {
63+
if ext.eq_ignore_ascii_case("meta") {
64+
return None;
65+
}
66+
}
67+
Some(file_path.to_owned())
68+
})
69+
.collect::<Vec<_>>();
70+
71+
let read_dir: Box<PathStream> = Box::new(stream::iter(mapped_stream));
72+
Ok(read_dir)
5473
}
5574

56-
async fn is_directory<'a>(&'a self, _path: &'a Path) -> Result<bool, AssetReaderError> {
57-
error!("Reading directories is not supported with the AndroidAssetReader");
58-
Ok(false)
75+
async fn is_directory<'a>(
76+
&'a self,
77+
path: &'a Path,
78+
) -> std::result::Result<bool, AssetReaderError> {
79+
let asset_manager = bevy_winit::ANDROID_APP
80+
.get()
81+
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
82+
.asset_manager();
83+
// HACK: `AssetManager` does not provide a way to check if path
84+
// points to a directory or a file
85+
// `open_dir` succeeds for both files and directories and will only
86+
// fail if the path does not exist at all
87+
// `open` will fail for directories, but it will work for files
88+
// The solution here was to first use `open_dir` to eliminate the case
89+
// when the path does not exist at all, and then to use `open` to
90+
// see if that path is a file or a directory
91+
let cpath = CString::new(path.to_str().unwrap()).unwrap();
92+
let _ = asset_manager
93+
.open_dir(&cpath)
94+
.ok_or(AssetReaderError::NotFound(path.to_path_buf()))?;
95+
Ok(asset_manager.open(&cpath).is_none())
5996
}
6097
}

0 commit comments

Comments
 (0)