Skip to content

Commit

Permalink
0.4.0 (#8)
Browse files Browse the repository at this point in the history
# **BREAKING CHANGE**
It is no longer necessary to prepend the main folder where the assets
are located when using `ImageAssetList`.

If you were doing

```rust
app.insert_resource(ImageAssetList::new(
    [
        "img/texture1.png",
        "img/texture2.png",
        "img/texture3.png",
    ]
    .into(),
));
```

change this to

```rust
app.insert_resource(ImageAssetList::new(
    [
        "texture1.png",
        "texture2.png",
        "texture3.png",
    ]
    .into(),
));
```

The folder will be taken from the `ImageAssetFolder` Resource.

# What was done
- Fixed issues that caused the plugin to hang forever in a `NotLoaded`
state.
- Refactored the plugin code.
- Updated README and added documentation for public types.
  • Loading branch information
mnmaita authored Aug 22, 2024
1 parent be4a8ae commit 9787e70
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 81 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "trve_bevy_image"
version = "0.3.0"
version = "0.4.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
46 changes: 31 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ trve_bevy_image = { git = "https://github.com/mnmaita/trve_bevy_image" }
Remember you can also target tags, commits and branches with this method:

```toml
trve_bevy_image = { git = "https://github.com/mnmaita/trve_bevy_image", tag = "v0.3.0" }
trve_bevy_image = { git = "https://github.com/mnmaita/trve_bevy_image", tag = "v0.4.0" }
```

```toml
trve_bevy_image = { git = "https://github.com/mnmaita/trve_bevy_image", branch = "test" }
```

```toml
trve_bevy_image = { git = "https://github.com/mnmaita/trve_bevy_image", rev = "03ee540ad7afba7822a73139169c635093127fba" }
trve_bevy_image = { git = "https://github.com/mnmaita/trve_bevy_image", rev = "some-sha" }
```

### Default usage and overriding default behavior
Expand All @@ -38,32 +38,48 @@ app.add_plugins(TrveImagePlugin);

// You insert this Resource and use the `new` function
// which accepts any parameter that can be turned into an `AssetPath`.
app.insert_resource(ImageAssetFolder::new("images"));
app.insert_resource(ImageAssetFolder::new("pngs"));
```

This will load all assets from `assets/images` by using `AssetServer`'s `load_folder` method.
This will load all assets from `assets/pngs` by using `AssetServer`'s `load_folder` method.

### Loading a list of assets

Certain platforms, like web, can't use `load_folder` to load assets so this library provides an override via the `ImageAssetList` Resource. This allows you to load a list of assets from your `assets` folder.
Certain platforms, like web, can't use `load_folder` to load assets so this library provides an override via the `ImageAssetList` Resource.

```rs
This allows you to load a list of assets from the folder specified in the `ImageAssetFolder` Resource, within the `assets` directory.

```rust
// This will attempt to load `assets/img/texture1.png`, `assets/img/texture2.png` and `assets/img/texture3.png`.
app.insert_resource(ImageAssetList::new(
[
"texture1.png",
"texture2.png",
"texture3.png",
]
.into(),
));
```

```rust
// This will attempt to load `assets/pngs/texture1.png`, `assets/pngs/texture2.png` and `assets/pngs/texture3.png`.
app.insert_resource(ImageAssetFolder::new("pngs"));
app.insert_resource(ImageAssetList::new(
[
"textures/player.png",
"textures/enemy.png",
"textures/background.png",
"texture1.png",
"texture2.png",
"texture3.png",
]
.into(),
));
```

If you insert this Resource, `ImageAssetFolder` will be ignored and the plugin will only load assets based on the provided list.
If you insert this Resource the plugin will **only** load the assets provided in the list.

## Bevy version compatibility

|trve_bevy_image|bevy|
|---|---|
|0.3|0.14|
|0.2|0.13|
|0.1|0.12|
| trve_bevy_image | bevy |
| --------------- | ---- |
| 0.3 0.4 | 0.14 |
| 0.2 | 0.13 |
| 0.1 | 0.12 |
168 changes: 103 additions & 65 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,24 @@ pub struct TrveImagePlugin;

impl Plugin for TrveImagePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ImageAssetFolder>();
app.init_resource::<ImageLoadState>();
app.add_systems(Startup, load_images);
app.add_systems(
Startup,
(setup_resources, load_images.after(setup_resources)),
);
app.add_systems(
Update,
update_image_assets_load_state.run_if(not(resource_equals(ImageLoadState::Loaded))),
update_image_assets_load_state.run_if(not(resource_equals(ImageLoadState::LOADED))),
);
}
}

/// Determines the name of the directory (within the `assets` directory) from where images will be loaded.
///
/// By default, this is set to "img".
///
/// Since `AssetServer::load_folder()` is unsupported in web builds, it will only be used as the base
/// directory for the file names in the `ImageAssetList` Resource.
#[derive(Resource)]
pub struct ImageAssetFolder<'a>(AssetPath<'a>);

Expand All @@ -28,43 +36,60 @@ impl<'a> ImageAssetFolder<'a> {
}
}

impl Default for ImageAssetFolder<'_> {
fn default() -> Self {
Self(IMAGE_ASSET_FOLDER.into())
}
}

impl std::fmt::Display for ImageAssetFolder<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}

/// List of assets to be loaded from the directory specified in the `ImageAssetFolder` Resource.
///
/// Should be a list of file names with their extension.
///
/// This works as an override for `ImageAssetFolder` in non-web platforms so, if set,
/// assets will be loaded individually and only from this list.
///
/// In web builds this is the default and the only supported option.
///
/// Example:
///
/// ```
/// app.insert_resource(ImageAssetList::new(
/// [
/// "image1.png",
/// "image2.png",
/// "image3.png",
/// ]
/// .to_vec(),
/// ));
/// ```
#[derive(Resource, Default, Deref)]
pub struct ImageAssetList<'a>(Vec<AssetPath<'a>>);

impl<'a> ImageAssetList<'a> {
pub fn new(path: Vec<impl Into<AssetPath<'a>>>) -> Self {
Self(
path.into_iter()
.map(|path| path.into())
.collect::<Vec<AssetPath<'a>>>(),
)
pub fn new(paths: Vec<impl Into<AssetPath<'a>>>) -> Self {
let asset_paths: Vec<AssetPath<'a>> = paths.into_iter().map(|path| path.into()).collect();
Self(asset_paths)
}
}

impl Default for ImageAssetFolder<'_> {
#[derive(Resource, PartialEq, Deref)]
struct ImageLoadState(RecursiveDependencyLoadState);

impl Default for ImageLoadState {
fn default() -> Self {
Self(IMAGE_ASSET_FOLDER.into())
Self(RecursiveDependencyLoadState::NotLoaded)
}
}

#[derive(Default, Resource, PartialEq)]
enum ImageLoadState {
#[default]
NotLoaded,
Loading,
Loaded,
Failed,
}

impl From<RecursiveDependencyLoadState> for ImageLoadState {
fn from(value: RecursiveDependencyLoadState) -> Self {
match value {
RecursiveDependencyLoadState::NotLoaded => Self::NotLoaded,
RecursiveDependencyLoadState::Loading => Self::Loading,
RecursiveDependencyLoadState::Loaded => Self::Loaded,
RecursiveDependencyLoadState::Failed => Self::Failed,
}
}
impl ImageLoadState {
const LOADED: Self = Self(RecursiveDependencyLoadState::Loaded);
}

#[derive(Resource, Default, Deref, DerefMut)]
Expand All @@ -73,63 +98,76 @@ struct ImageHandles(Vec<Handle<Image>>);
#[derive(Resource, Default, Deref, DerefMut)]
struct ImageFolderHandle(Handle<LoadedFolder>);

fn setup_resources(mut commands: Commands) {
commands.init_resource::<ImageAssetFolder>();

if cfg!(target_family = "wasm") {
commands.init_resource::<ImageAssetList>();
}
}

fn load_images(
mut commands: Commands,
asset_server: Res<AssetServer>,
image_folder: Res<ImageAssetFolder<'static>>,
image_asset_list: Option<Res<ImageAssetList<'static>>>,
) {
if cfg!(not(target_family = "wasm")) && image_asset_list.is_none() {
commands.insert_resource(ImageFolderHandle(
asset_server.load_folder(image_folder.0.clone()),
));
return;
if cfg!(not(target_family = "wasm")) {
if image_asset_list.is_none() {
// TODO: Verify that files in the directory are actually Image handles
commands.insert_resource(ImageFolderHandle(
asset_server.load_folder(image_folder.0.clone()),
));
return;
}
}

if let Some(image_asset_list) = image_asset_list {
if image_asset_list.is_empty() {
if cfg!(target_family = "wasm") {
info!("ImageAssetList Resource is empty.");
}
} else {
commands.insert_resource(ImageHandles(
image_asset_list
.iter()
.map(|path| asset_server.load::<Image>(path))
.collect::<Vec<Handle<Image>>>(),
));
}
} else if cfg!(target_family = "wasm") {
warn!("ImageAssetList Resource does not exist.");
let load_image_asset =
|path| asset_server.load::<Image>(format!("{}/{path}", *image_folder));
let handles: Vec<Handle<Image>> = match image_asset_list.is_empty() {
true => Vec::default(),
false => image_asset_list.iter().map(load_image_asset).collect(),
};
commands.insert_resource(ImageHandles(handles));
}
}

fn update_image_assets_load_state(
mut textures_load_state: ResMut<ImageLoadState>,
mut image_load_state: ResMut<ImageLoadState>,
asset_server: Res<AssetServer>,
image_handles: Option<Res<ImageHandles>>,
image_folder_handle: Option<Res<ImageFolderHandle>>,
image_asset_list: Option<Res<ImageAssetList<'static>>>,
) {
if image_asset_list.is_some() {
if let Some(image_handles) = image_handles {
let all_loaded = image_handles.iter().all(|handle| {
asset_server.recursive_dependency_load_state(handle.id())
== RecursiveDependencyLoadState::Loaded
});
*textures_load_state = if all_loaded {
RecursiveDependencyLoadState::Loaded.into()
} else {
RecursiveDependencyLoadState::NotLoaded.into()
}
if cfg!(not(target_family = "wasm")) {
if image_asset_list.is_none() {
image_load_state.0 =
asset_server.recursive_dependency_load_state(&image_folder_handle.unwrap().0);
return;
}
} else if let Some(image_folder_handle) = image_folder_handle {
*textures_load_state = asset_server
.recursive_dependency_load_state(&image_folder_handle.0)
.into()
}

if let Some(image_handles) = image_handles {
let all_loaded = image_handles.iter().all(|handle| {
if RecursiveDependencyLoadState::Failed
== asset_server.recursive_dependency_load_state(handle)
{
if let Some(path) = handle.path() {
info!("Asset '{path}' failed to load. Make sure the file name is correct and is an image.");
}
return true;
}
asset_server.is_loaded_with_dependencies(handle)
});

image_load_state.0 = match all_loaded {
true => RecursiveDependencyLoadState::Loaded,
false => RecursiveDependencyLoadState::NotLoaded,
};
}
}

pub fn image_assets_loaded() -> impl Condition<()> {
IntoSystem::into_system(resource_equals(ImageLoadState::Loaded))
IntoSystem::into_system(resource_equals(ImageLoadState::LOADED))
}

0 comments on commit 9787e70

Please sign in to comment.