bevy-atlas-loader 0.1.0

Bevy plugin for defining and loading 2D TextureAtlas images.
Documentation
use bevy::{asset::LoadState, prelude::*, sprite::TextureAtlas};
use std::{any::type_name, marker::PhantomData};
use strum::VariantNames;

use crate::{
    AtlasDefinition, AtlasTextures, AtlasTexturesEvent, CreatedAtlas, DefinitionProcessState,
    FolderAtlasDefinition, GenericAtlasDefinitions, GetTextureAtlas, GridAtlasDefinition,
    MultiTextureProcessState, PatchAtlasDefinition, ResourceStatus, SingleTextureProcessState,
    TypedAtlasDefinition,
};

#[allow(unused)]
pub fn atlas_textures_failed<T>(handle: Option<Res<TypedAtlasDefinition<T>>>) -> bool
where
    T: Send + Sync + 'static,
{
    if let Some(handle) = handle {
        handle.state.is_failed()
    } else {
        false
    }
}

#[allow(unused)]
pub fn atlas_textures_created<T>(handle: Option<Res<TypedAtlasDefinition<T>>>) -> bool
where
    T: Send + Sync + 'static,
{
    if let Some(handle) = handle {
        handle.state.is_done()
    } else {
        false
    }
}

#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn process_atlas_definitions<T>(
    definition_handle: Option<ResMut<TypedAtlasDefinition<T>>>,
    mut commands: Commands,
    asset_server: Res<AssetServer>,
    mut atlas_definitions: ResMut<Assets<GenericAtlasDefinitions>>,
    mut texture_atlases: ResMut<Assets<TextureAtlas>>,
    mut texture_images: ResMut<Assets<Image>>,
    atlas_definition_events: EventReader<AssetEvent<GenericAtlasDefinitions>>,
    mut atlas_texture_event: EventWriter<AtlasTexturesEvent<T>>,
) where
    T: VariantNames + std::str::FromStr,
    T: Eq + std::hash::Hash + Send + Sync + 'static,
    <T as std::str::FromStr>::Err: std::fmt::Debug,
{
    if let Some(mut definition_handle) = definition_handle {
        match definition_handle.state {
            DefinitionProcessState::Loading => {
                if match &definition_handle.definitions {
                    crate::DefinitionsType::Direct(_) => true,
                    crate::DefinitionsType::Indirect(handle) => {
                        asset_server.get_load_state(handle) == LoadState::Loaded
                    }
                } {
                    debug!(
                        T = type_name::<T>(),
                        "Verifying all AtlasDefinitions<T> are present."
                    );
                    let atlas_definitions = match &definition_handle.definitions {
                        crate::DefinitionsType::Direct(definitions) => definitions.as_ref(),
                        crate::DefinitionsType::Indirect(handle) => atlas_definitions
                            .get(handle.id)
                            .expect("AtlasDefinitions asset should be present."),
                    };
                    definition_handle.state = T::VARIANTS
                        .iter()
                        .filter(|&&variant| !atlas_definitions.contains_key(variant))
                        .fold(DefinitionProcessState::Processing, |_, &variant| {
                            error!(
                                T = type_name::<T>(),
                                Variant = variant,
                                variant,
                                "Missing AtlasDefinition<T> for variant."
                            );
                            let event_writer = &mut atlas_texture_event;
                            event_writer.send(AtlasTexturesEvent::<T>(
                                ResourceStatus::Failed,
                                PhantomData::default(),
                            ));
                            DefinitionProcessState::Failed
                        });
                }
            }
            DefinitionProcessState::Processing => {
                let definition_handle = &mut *definition_handle;
                let atlas_definitions = match definition_handle.definitions {
                    crate::DefinitionsType::Direct(ref mut definitions) => definitions.as_mut(),
                    crate::DefinitionsType::Indirect(ref mut handle) => atlas_definitions
                        .get_mut(handle.id)
                        .expect("AtlasDefinitions asset should be present."),
                };
                definition_handle.state = process_generic_atlas_definitions(
                    atlas_definitions,
                    &asset_server,
                    &mut texture_atlases,
                    &mut texture_images,
                );
                if definition_handle.state == DefinitionProcessState::Finalizing {
                    info!(T = type_name::<T>(), "AtlasTexture<T> created for all T.");
                    let map = atlas_definitions.iter().map(|(key, definition)| {
                        (
                            key.clone(),
                            definition
                                .texture_atlas()
                                .cloned()
                                .expect("Atlas not created, though all definitions are present."),
                        )
                    });
                    commands.insert_resource(AtlasTextures::<T>(
                        map.map(|(key, handle)| {
                            let key = T::from_str(&key).unwrap();
                            let len = texture_atlases.get(&handle).unwrap().len();
                            (key, CreatedAtlas { handle, len })
                        })
                        .collect(),
                    ));
                }
            }
            DefinitionProcessState::Finalizing => {
                let mut event_writer = atlas_texture_event;
                event_writer.send(AtlasTexturesEvent::<T>(
                    ResourceStatus::Created,
                    PhantomData::default(),
                ));
                definition_handle.state = DefinitionProcessState::Done
            }
            DefinitionProcessState::Done | DefinitionProcessState::Failed => {
                let mut event_reader = atlas_definition_events;
                for ev in event_reader.iter() {
                    match ev {
                        AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
                            match &definition_handle.definitions {
                                crate::DefinitionsType::Indirect(h) if h == handle => {
                                    warn!(
                                        T = type_name::<T>(),
                                        "AtlasDefinitions<T> has changed. Recreating atlas."
                                    );
                                    definition_handle.state = DefinitionProcessState::Loading;
                                }
                                _ => {}
                            }
                        }
                        AssetEvent::Removed { .. } => {
                            error!("AtlasDefinitions should never be removed!")
                        }
                    }
                }
            }
        }
    }
}

fn process_generic_atlas_definitions(
    atlas_definitions: &mut GenericAtlasDefinitions,
    asset_server: &AssetServer,
    texture_atlases: &mut Assets<TextureAtlas>,
    texture_images: &mut Assets<Image>,
) -> DefinitionProcessState {
    if atlas_definitions.iter_mut().all(|(_key, cfg)| match cfg {
        AtlasDefinition::Grid(grid_definition) => {
            process_grid_atlas_definition(grid_definition, asset_server, texture_atlases)
        }
        AtlasDefinition::Manual(patch_definition) => {
            process_patch_atlas_definition(patch_definition, asset_server, texture_atlases)
        }
        AtlasDefinition::Folder(folder_definition) => process_folder_atlas_definition(
            folder_definition,
            asset_server,
            texture_atlases,
            texture_images,
        ),
    }) {
        DefinitionProcessState::Finalizing
    } else {
        DefinitionProcessState::Processing
    }
}

fn process_grid_atlas_definition(
    grid_definition: &mut GridAtlasDefinition,
    asset_server: &AssetServer,
    texture_atlases: &mut Assets<TextureAtlas>,
) -> bool {
    match &grid_definition.state {
        SingleTextureProcessState::None => {
            grid_definition.state = SingleTextureProcessState::LoadingTexture(
                asset_server.load_untyped(grid_definition.texture.as_path()),
            );
            false
        }
        SingleTextureProcessState::LoadingTexture(handle) => {
            let image = handle.clone().typed::<Image>();
            if asset_server.get_load_state(&image) == LoadState::Loaded {
                let atlas = TextureAtlas::from_grid_with_padding(
                    image,
                    Vec2::new(
                        grid_definition.tile_size.0 as f32,
                        grid_definition.tile_size.1 as f32,
                    ),
                    grid_definition.columns,
                    grid_definition.rows,
                    match grid_definition.padding {
                        Some((x, y)) => Vec2::new(x as f32, y as f32),
                        None => Vec2::ZERO,
                    },
                );
                grid_definition.state =
                    SingleTextureProcessState::AtlasCreated(texture_atlases.add(atlas));
            }
            false
        }
        SingleTextureProcessState::AtlasCreated(_) => true,
    }
}

fn process_patch_atlas_definition(
    patch_definition: &mut PatchAtlasDefinition,
    asset_server: &AssetServer,
    texture_atlases: &mut Assets<TextureAtlas>,
) -> bool {
    match &patch_definition.state {
        SingleTextureProcessState::None => {
            patch_definition.state = SingleTextureProcessState::LoadingTexture(
                asset_server.load_untyped(patch_definition.texture.as_path()),
            );
            false
        }
        SingleTextureProcessState::LoadingTexture(handle) => {
            let image = handle.clone().typed::<Image>();
            if asset_server.get_load_state(&image) == LoadState::Loaded {
                let mut atlas = TextureAtlas::new_empty(
                    image,
                    Vec2::new(
                        patch_definition.width as f32,
                        patch_definition.height as f32,
                    ),
                );
                for &(x, y) in patch_definition.positions.iter() {
                    atlas.add_texture(bevy::sprite::Rect {
                        min: Vec2::new(x as f32, y as f32),
                        max: Vec2::new(
                            (x + patch_definition.width) as f32,
                            (y + patch_definition.height) as f32,
                        ),
                    });
                }
                patch_definition.state =
                    SingleTextureProcessState::AtlasCreated(texture_atlases.add(atlas));
            }
            false
        }
        SingleTextureProcessState::AtlasCreated(_) => true,
    }
}

fn process_folder_atlas_definition(
    folder_definition: &mut FolderAtlasDefinition,
    asset_server: &AssetServer,
    texture_atlases: &mut Assets<TextureAtlas>,
    texture_images: &mut Assets<Image>,
) -> bool {
    match &folder_definition.state {
        MultiTextureProcessState::None => {
            folder_definition.state = MultiTextureProcessState::LoadingTextures(
                asset_server
                    .load_folder(folder_definition.path.as_path())
                    .expect("path must exist and be a folder"),
            );
            false
        }
        MultiTextureProcessState::LoadingTextures(handles) => {
            let mut texture_atlas_builder = TextureAtlasBuilder::default();
            for handle in handles {
                let texture = texture_images.get(handle.id).unwrap();
                texture_atlas_builder.add_texture(handle.clone().typed::<Image>(), texture);
            }
            let atlas = texture_atlas_builder.finish(texture_images).unwrap();
            folder_definition.state =
                MultiTextureProcessState::AtlasCreated(texture_atlases.add(atlas));
            false
        }
        MultiTextureProcessState::AtlasCreated(_) => true,
    }
}