My personal project and infrastructure archive
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
nomicon/games/rstnode/rst-client/src/assets.rs

153 lines
4.8 KiB

use crate::{error::LoadError, GameSettings};
use cairo::{Context, Format, ImageSurface, Rectangle};
use ggez::graphics::Image;
use librsvg::{CairoRenderer, Loader};
use std::{
collections::BTreeMap,
ffi::OsStr,
fs::{read_dir, File},
io::BufWriter,
io::Read,
path::{Path, PathBuf},
};
use tempfile::tempdir;
pub use rst_core::loader::Uri;
pub type Result<T> = std::result::Result<T, LoadError>;
/// Asset loader
#[derive(Debug)]
pub struct Assets {
inner: BTreeMap<Uri, Image>,
}
impl Assets {
fn new() -> Self {
Self {
inner: Default::default(),
}
}
pub fn find<U: Into<Uri>>(&self, u: U) -> Option<Image> {
self.inner.get(&u.into()).map(|i| i.clone())
}
/// Load an asset directory path
fn load_tree(&mut self, ctx: &mut ggez::Context, tmpdir: &Path, p: &Path) -> Result<()> {
let err: LoadError = p.to_str().unwrap().into();
read_dir(p)
.map_err(|_| err)?
.map(|e| {
let e = e.unwrap();
let p = e.path();
let ext = OsStr::new("svg");
if p.is_dir() {
debug!(
"Entering directory {}",
p.file_name().unwrap().to_str().unwrap()
);
self.load_tree(ctx, tmpdir, p.as_path())?;
} else if p.extension() == Some(ext) {
let png = load_svg(tmpdir, p.as_path())?;
let basepath = p.with_extension("");
let uri_cat = p.parent().unwrap().file_name().unwrap().to_str().unwrap();
let name = basepath.file_name().unwrap().to_str().unwrap();
let uri = format!("{}/{}", uri_cat, name);
let path_str = png.as_path().to_str().unwrap();
let mut content = vec![];
let mut f = File::open(png.as_path()).map_err(|_| {
LoadError::from(format!("No such file: {}", path_str).as_str())
})?;
f.read_to_end(&mut content).map_err(|e| {
LoadError::from(
format!("Read error for {}: {}", path_str, e.to_string()).as_str(),
)
})?;
self.inner.insert(
uri.into(),
Image::from_bytes(ctx, content.as_slice()).map_err(|e| {
LoadError::from(
format!("Read error for {}: {}", path_str, e.to_string()).as_str(),
)
})?,
);
}
Ok(())
})
.fold(Ok(()), |acc, res| match (acc, res) {
(Ok(_), Ok(_)) => Ok(()),
(Ok(_), Err(e)) => Err(e),
(Err(e), _) => Err(e),
})
}
}
/// Load all game assets into the game
///
/// This function performs three main steps.
///
/// 1. Check that the provided path is a directory
/// 2. Recursively load Directories and files and call
/// [`load_svg`](self::load_svg) on each `.svg` file
/// 3. Re-load newly converted assets into [`Assets`](self::Assets)
pub fn load_tree(ctx: &mut ggez::Context, settings: &GameSettings) -> Result<Assets> {
let path = match settings.assets.clone() {
Some(s) => Ok(s),
None => Err(LoadError::from("No assets path set!")),
}?;
debug!(
"Starting assets loading harness on {}",
path.to_str().unwrap()
);
let tmpdir = tempdir().unwrap();
let mut assets = Assets::new();
assets.load_tree(ctx, tmpdir.path(), path.as_path())?;
info!("Asset loading complete!");
Ok(assets)
}
/// A utility function to take an SVG and render it to a raster image
/// according to a render spec
pub fn load_svg(tmpdir: &Path, p: &Path) -> Result<PathBuf> {
let err: LoadError = p.to_str().unwrap().into();
let handle = Loader::new().read_path(p).map_err(|_| err.clone())?;
let renderer = CairoRenderer::new(&handle);
let surf = ImageSurface::create(Format::ARgb32, 256, 256).map_err(|_| err.clone())?;
let cr = Context::new(&surf);
renderer
.render_document(
&cr,
&Rectangle {
x: 0.0,
y: 0.0,
width: 256.0,
height: 256.0,
},
)
.map_err(|_| err.clone())?;
let png = p.with_extension("png");
let name = png
.file_name()
.map_or_else(|| Err(err.clone()), |name| Ok(name))?;
let out = tmpdir.join(name.clone());
let mut file = BufWriter::new(File::create(out.clone()).map_err(|_| err.clone())?);
surf.write_to_png(&mut file).map_err(|_| err.clone())?;
Ok(out.to_path_buf())
}