Skip to content

Instantly share code, notes, and snippets.

@xacrimon
Created September 6, 2021 22:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xacrimon/bfd7a782b107e515c72a35374889f539 to your computer and use it in GitHub Desktop.
Save xacrimon/bfd7a782b107e515c72a35374889f539 to your computer and use it in GitHub Desktop.
use super::gpu::Gpu;
use super::stb_image::{LoadImageError, Rgba8Image};
use bus::{Bus, BusReader};
use log::{debug, info};
use nanoserde::{DeRon, SerRon};
use std::env;
use std::{
collections::HashMap,
fs, io,
path::{Path, PathBuf},
};
use thiserror::Error;
use walkdir::{DirEntry, WalkDir};
pub fn find_root() -> Option<PathBuf> {
let mut containing_exe = env::current_exe().ok()?;
containing_exe.pop();
containing_exe.push("assets");
if is_asset_dir(&containing_exe) {
return Some(containing_exe);
}
let mut work_dir = env::current_dir().ok()?;
work_dir.push("assets");
if is_asset_dir(&work_dir) {
return Some(work_dir);
}
None
}
fn is_asset_dir(path: &Path) -> bool {
std::fs::metadata(path)
.map(|meta| meta.is_dir())
.unwrap_or(false)
}
pub struct AssetStore {
asset_root: PathBuf,
assets: HashMap<String, Asset>,
notifier: Bus<()>,
}
impl AssetStore {
pub fn new() -> Self {
Self {
asset_root: PathBuf::new(),
assets: HashMap::new(),
notifier: Bus::new(32),
}
}
pub fn load_from(mut self, asset_root: PathBuf, gpu: &Gpu) -> Result<Self, AssetLoadError> {
info!("loading assets");
self.asset_root = asset_root;
for entry in WalkDir::new(&self.asset_root) {
let entry = entry?;
if !is_definition(&entry) {
continue;
}
debug!("loading asset {:?}", entry.path());
let raw = load_definition(&entry)?;
let definition = parse_definition(&raw)?;
let (identifier, asset) = definition.load(gpu, entry.path().parent().unwrap())?;
self.assets.insert(identifier, asset);
}
Ok(self)
}
pub fn reload(&mut self, gpu: &Gpu) -> Result<(), AssetLoadError> {
info!("reloading assets");
self.assets.clear();
for entry in WalkDir::new(&self.asset_root) {
let entry = entry?;
if !is_definition(&entry) {
continue;
}
debug!("loading asset {:?}", entry.path());
let raw = load_definition(&entry)?;
let definition = parse_definition(&raw)?;
let (identifier, asset) = definition.load(gpu, entry.path().parent().unwrap())?;
self.assets.insert(identifier, asset);
}
self.notifier.broadcast(());
Ok(())
}
#[allow(dead_code)]
pub fn subscribe(&mut self) -> BusReader<()> {
self.notifier.add_rx()
}
pub fn get_sprite(&self, identifier: &str) -> Option<&wgpu::Texture> {
if let Asset::Sprite(texture) = &self.assets[identifier] {
Some(texture)
} else {
None
}
}
pub fn get_shader(&self, identifier: &str) -> Option<&wgpu::ShaderModule> {
if let Asset::Shader(shader) = &self.assets[identifier] {
Some(shader)
} else {
None
}
}
}
fn parse_definition(raw: &str) -> Result<AssetDefinition, nanoserde::DeRonErr> {
DeRon::deserialize_ron(raw)
}
fn load_definition(item: &DirEntry) -> Result<String, io::Error> {
fs::read_to_string(item.path())
}
fn is_definition(entry: &DirEntry) -> bool {
entry.file_type().is_file() && entry.file_name().to_str().unwrap_or("").ends_with(".ron")
}
enum Asset {
Sprite(wgpu::Texture),
Shader(wgpu::ShaderModule),
}
#[derive(SerRon, DeRon)]
enum AssetDefinition {
Sprite { identifier: String, path: String },
Shader { identifier: String, path: String },
}
impl AssetDefinition {
fn load(self, gpu: &Gpu, relative_to: &Path) -> Result<(String, Asset), AssetLoadError> {
match self {
Self::Sprite { identifier, path } => load_sprite(gpu, identifier, &path, relative_to),
Self::Shader { identifier, path } => load_shader(gpu, identifier, &path, relative_to),
}
}
}
fn load_sprite(
gpu: &Gpu,
identifier: String,
path: &str,
relative_to: &Path,
) -> Result<(String, Asset), AssetLoadError> {
let full_path = get_full_path(path, relative_to);
let image = Rgba8Image::load(&full_path)?;
let texture = gpu.create_2d_srgb_texture(
image.width(),
image.height(),
wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
Some(&identifier),
&image,
);
Ok((identifier, Asset::Sprite(texture)))
}
fn load_shader(
gpu: &Gpu,
identifier: String,
path: &str,
relative_to: &Path,
) -> Result<(String, Asset), AssetLoadError> {
let full_path = get_full_path(path, relative_to);
let source = fs::read_to_string(&full_path)?;
let shader = gpu.create_shader_module(Some(&identifier), &source);
Ok((identifier, Asset::Shader(shader)))
}
fn get_full_path(path: &str, relative_to: &Path) -> PathBuf {
let mut full_path = PathBuf::new();
full_path.extend(relative_to.components());
full_path.push(path);
full_path
}
#[derive(Error, Debug)]
pub enum AssetLoadError {
#[error("failed to parse asset definition: {0}")]
DefinitionParse(#[from] nanoserde::DeRonErr),
#[error("io operation failed: {0}")]
Io(#[from] io::Error),
#[error("failed to search assets: {0}")]
WalkDir(#[from] walkdir::Error),
#[error("failed to load image: {0}")]
LoadImage(#[from] LoadImageError),
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment