Created
September 6, 2021 22:30
-
-
Save xacrimon/bfd7a782b107e515c72a35374889f539 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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