bevy_tmx 0.2.0

Scene loader for .tmx files created by the Tiled map editor
Documentation
use std::hash::Hasher;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::*;
use async_mutex::Mutex;
#[cfg(feature = "plugin")]
use bevy_asset::{Handle, LoadContext, LoadedAsset};
#[cfg(feature = "plugin")]
use bevy_render::texture::{Extent3d, Texture as BevyTexture, TextureDimension, TextureFormat};
use image::{load_from_memory, GenericImage, RgbaImage};

/// A shared image
#[derive(Clone)]
pub struct Texture {
    data: Arc<Mutex<Inner>>,
    label: Arc<str>,
    width: u32,
    height: u32,
}

enum Inner {
    Defined {
        path: PathBuf,
    },
    Decoded {
        buffer: RgbaImage,
    },
    #[cfg(feature = "plugin")]
    Loaded {
        handle: Handle<BevyTexture>,
    },
}

pub(crate) struct TexturePtr(Arc<str>);

impl Texture {
    pub(crate) fn from_bytes(data: &[u8], label: impl Into<Arc<str>>) -> Result<Self> {
        let buffer = load_from_memory(data)?.to_rgba8();
        let width = buffer.width();
        let height = buffer.height();
        Ok(Texture {
            data: Arc::new(Mutex::new(Inner::Decoded { buffer })),
            label: label.into(),
            width,
            height,
        })
    }

    pub(crate) fn from_path(path: PathBuf) -> Self {
        let label = format!("{}", path.display()).into();
        Texture {
            data: Arc::new(Mutex::new(Inner::Defined { path })),
            label,
            width: 0,
            height: 0,
        }
    }

    pub(crate) async fn resize(&self, width: u32, height: u32) -> Result<Self> {
        if width != self.width && height != self.height {
            let data = self.data.lock().await;
            match &*data {
                Inner::Defined { path } => Ok(Texture {
                    data: Arc::new(Mutex::new(Inner::Defined { path: path.clone() })),
                    label: format!("{}#{}x{}", self.label, width, height).into(),
                    width,
                    height,
                }),
                Inner::Decoded { buffer } => {
                    let mut new_image: RgbaImage = RgbaImage::new(width, height);
                    new_image.copy_from(buffer, 0, 0)?;
                    Ok(Texture {
                        data: Arc::new(Mutex::new(Inner::Decoded { buffer: new_image })),
                        label: format!("{}#{}x{}", self.label, width, height).into(),
                        width,
                        height,
                    })
                }
                #[cfg(feature = "plugin")]
                Inner::Loaded { .. } => unreachable!(),
            }
        } else {
            Ok(self.clone())
        }
    }

    #[cfg(feature = "plugin")]
    pub(crate) async fn load(
        &self,
        load_context: &mut LoadContext<'_>,
    ) -> Result<Handle<BevyTexture>> {
        let mut data = self.data.lock().await;

        let handle = match &mut *data {
            Inner::Defined { path } => {
                let mut buffer =
                    load_from_memory(load_context.read_asset_bytes(path).await?.as_slice())?
                        .to_rgba8();
                if self.width > 0 && self.height > 0 {
                    let mut new_image: RgbaImage = RgbaImage::new(self.width, self.height);
                    new_image.copy_from(&buffer, 0, 0)?;
                    buffer = new_image;
                }

                load_context.set_labeled_asset(
                    self.label.as_ref(),
                    LoadedAsset::new(BevyTexture::new(
                        Extent3d {
                            width: buffer.width(),
                            height: buffer.height(),
                            depth: 1,
                        },
                        TextureDimension::D2,
                        buffer.into_raw(),
                        TextureFormat::Rgba8Unorm,
                    )),
                )
            }
            Inner::Decoded { buffer } => load_context.set_labeled_asset(
                self.label.as_ref(),
                LoadedAsset::new(BevyTexture::new(
                    Extent3d {
                        width: self.width,
                        height: self.height,
                        depth: 1,
                    },
                    TextureDimension::D2,
                    std::mem::take(buffer).into_raw(),
                    TextureFormat::Rgba8Unorm,
                )),
            ),
            Inner::Loaded { handle } => handle.clone(),
        };

        *data = Inner::Loaded {
            handle: handle.clone(),
        };

        Ok(handle)
    }

    pub(crate) fn width(&self) -> u32 {
        self.width
    }

    pub(crate) fn height(&self) -> u32 {
        self.height
    }
}

impl From<&Texture> for TexturePtr {
    fn from(image: &Texture) -> Self {
        Self(image.label.clone())
    }
}

impl std::hash::Hash for TexturePtr {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.0.hash(state)
    }
}

impl std::cmp::PartialEq for TexturePtr {
    fn eq(&self, other: &Self) -> bool {
        self.0.eq(&other.0)
    }
}

impl std::cmp::Eq for TexturePtr {}