use std::borrow::Cow;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use crate::manifest::{self, Manifest};
use crate::pack::Pack;
use crate::{Error, Kind, Result};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Entry {
pub id: u64,
pub kind: Kind,
pub path: String,
}
pub trait Decode: Sized {
fn decode(bytes: &[u8]) -> Result<Self>;
}
macro_rules! decode {
($($ty:ty),*) => {
$(impl Decode for $ty {
fn decode(bytes: &[u8]) -> Result<Self> {
<$ty>::decode(bytes)
}
})*
};
}
decode!(
crate::Texture,
crate::Material,
crate::Manifest,
crate::Mesh,
crate::Animation,
crate::Font,
crate::Atlas,
crate::Schema,
crate::Data,
crate::Graph,
crate::Tree,
crate::GradientSet,
crate::Palette,
crate::Shader,
crate::CustomMaterial
);
enum Source {
Pack(Pack),
Dir(PathBuf),
}
pub struct Assets {
source: Source,
entries: Vec<Entry>,
by_id: HashMap<u64, usize>,
}
impl Assets {
pub fn open(path: impl AsRef<Path>) -> Result<Self> {
let path = path.as_ref();
if path.is_dir() {
let bytes = std::fs::read(path.join(manifest::FILE_NAME))?;
let manifest = Manifest::decode(&bytes).map_err(|e| e.in_file(manifest::FILE_NAME))?;
let entries = manifest
.entries
.into_iter()
.map(|e| Entry {
id: e.id,
kind: e.kind,
path: e.path,
})
.collect();
Ok(Self::new(Source::Dir(path.to_path_buf()), entries))
} else {
Self::from_pack(std::fs::read(path)?)
}
}
pub fn from_pack(bytes: Vec<u8>) -> Result<Self> {
let pack = Pack::decode(bytes)?;
let entries = pack
.entries
.iter()
.map(|e| Entry {
id: e.id,
kind: e.kind,
path: e.path.clone(),
})
.collect();
Ok(Self::new(Source::Pack(pack), entries))
}
fn new(source: Source, mut entries: Vec<Entry>) -> Self {
entries.sort_by(|a, b| a.path.cmp(&b.path));
let by_id = entries.iter().enumerate().map(|(i, e)| (e.id, i)).collect();
Self {
source,
entries,
by_id,
}
}
pub fn pack(&self) -> Option<&Pack> {
match &self.source {
Source::Pack(pack) => Some(pack),
Source::Dir(_) => None,
}
}
pub fn entries(&self) -> &[Entry] {
&self.entries
}
pub fn of_kind(&self, kind: Kind) -> impl Iterator<Item = &Entry> {
self.entries.iter().filter(move |e| e.kind == kind)
}
pub fn entry(&self, id: u64) -> Option<&Entry> {
self.by_id.get(&id).map(|&i| &self.entries[i])
}
pub fn find(&self, path: &str) -> Option<&Entry> {
self.entries
.binary_search_by(|e| e.path.as_str().cmp(path))
.ok()
.map(|i| &self.entries[i])
}
pub fn read(&self, id: u64) -> Result<Cow<'_, [u8]>> {
let entry = self
.entry(id)
.ok_or_else(|| Error::NotFound(format!("asset {id:#018x}")))?;
self.read_entry(entry)
}
pub fn read_path(&self, path: &str) -> Result<Cow<'_, [u8]>> {
let entry = self
.find(path)
.ok_or_else(|| Error::NotFound(path.to_owned()))?;
self.read_entry(entry)
}
fn read_entry(&self, entry: &Entry) -> Result<Cow<'_, [u8]>> {
match &self.source {
Source::Pack(pack) => {
let found = pack
.find(&entry.path)
.ok_or_else(|| Error::NotFound(entry.path.clone()))?;
Ok(Cow::Borrowed(pack.bytes(found)))
}
Source::Dir(dir) => Ok(Cow::Owned(std::fs::read(dir.join(&entry.path))?)),
}
}
pub fn load<T: Decode>(&self, id: u64) -> Result<T> {
let path = self.entry(id).map(|e| e.path.clone()).unwrap_or_default();
T::decode(&self.read(id)?).map_err(|e| e.in_file(&path))
}
pub fn load_path<T: Decode>(&self, path: &str) -> Result<T> {
T::decode(&self.read_path(path)?).map_err(|e| e.in_file(path))
}
}