burntnail-utils 0.7.1

Set of Utilities for Errors, and some cachers/timers/macros
Documentation
//! This functions as a basic cacher for `Piston2D` images
//!
//! ## Usage
//! ```rust
//!
//! use burntnail_utils::piston_cache::Cacher;
//! let mut cacher = Cacher::new(&mut get_anything_for_docs(), Some("assets"));
//!
//! //then, we can either insert a bunch of textures on start
//! cacher.insert("sprite.png")?;
//! cacher.insert("bg.png")?;
//!
//! //or, just grab them as and when we need them
//! cacher.get("highly-specific-level-thingie.png");
//!
//! ```

use crate::time_based_structs::scoped_timers::ScopedTimer;
use find_folder::Search::ParentsThenKids;
use piston_window::{
    Filter, Flip, G2dTexture, G2dTextureContext, PistonWindow, Texture, TextureSettings,
};
use std::{collections::HashMap, path::PathBuf, result::Result as SResult};

use crate::{
    error_ext::ToErr,
    error_types::{BError, BResult},
};

///Struct to hold a cache of [`G2dTexture`]s
pub struct Cacher {
    ///Base path for the assets
    base_path: PathBuf,
    ///HashMap of paths to textures
    assets: HashMap<String, G2dTexture>,
    ///Context for textures from window
    tc: G2dTextureContext,
}

impl Cacher {
    ///Function to create a new empty cache.
    ///
    /// # Errors
    /// Can fail if it can't find the assets folder
    fn base_new(win: &mut PistonWindow, path: Option<&str>) -> SResult<Self, find_folder::Error> {
        let path = ParentsThenKids(2, 2).for_folder(path.unwrap_or("assets"))?;

        Ok(Self {
            base_path: path,
            assets: HashMap::new(),
            tc: win.create_texture_context(),
        })
    }

    ///Base function for getting something
    ///
    ///Takes a relative path, returns either `Err(String)` from insertion, or an `Ok(G2DTexture)` with the result from the hashmap if insertion had no errors
    fn base_get(&mut self, p: &str) -> SResult<&G2dTexture, String> {
        self.base_insert(p)
            .map(|_| {
                self.assets
                    .get(p)
                    .ok_or_else(|| "Asset missing in internal storage".into())
            })
            .and_then(std::convert::identity) //Taken from the unstable code, issue: 70142, nice code: `.flatten()`
    }

    ///Base function for inserting something.
    ///
    ///Takes a relative path, returns `Ok` if all worked or element already existed, else returns `Err(String)` if failure
    fn base_insert(&mut self, p: &str) -> SResult<(), String> {
        if self.assets.contains_key(p) {
            return Ok(());
        }

        #[cfg(feature = "tracing")]
        tracing::trace!("Inserting {p}");
        #[cfg(not(feature = "tracing"))]
        println!("Inserting {p}");

        let _st = ScopedTimer::new(format!("Geting {p}"));

        let path = self.base_path.join(p);
        let ts = TextureSettings::new().filter(Filter::Nearest);

        match Texture::from_path(&mut self.tc, path, Flip::None, &ts) {
            Ok(tex) => {
                self.assets.insert(p.to_string(), tex);
                Ok(())
            }
            Err(e) => Err(e),
        }
    }
}

impl Cacher {
    ///Function to create a new empty cache.
    ///
    /// # Errors
    /// Can fail if it can't find the assets folder
    pub fn new(win: &mut PistonWindow, path: Option<&str>) -> BResult<Self> {
        Self::base_new(win, path).ae()
    }

    ///Gets a [`G2dTexture`] from the cache. Returns [`None`] if there is no asset with that path.
    ///
    /// # Errors
    /// - Unable to find the texture using [`Texture::from_path`]
    pub fn get(&mut self, p: &str) -> BResult<&G2dTexture> {
        match self.base_get(p) {
            Ok(tex) => Ok(tex),
            Err(e) => Err(BError::msg(format!("Texture Get Error: {e}"))),
        }
    }

    ///Inserts a new asset into the cache from the path given - should just be like `'icon.png'`, as all files should be in the `'assets/'` folder
    ///
    /// # Errors
    /// - Unable to find the texture using [`Texture::from_path`]
    pub fn insert(&mut self, p: &str) -> BResult<()> {
        self.base_insert(p)
            .map_err(|s| BError::msg(format!("Texture Insert Error: {s}")))
    }
}