use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter};
use std::path::Path;
use serde::{Deserialize, Serialize};
use super::asset_uuid::AssetUuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum AssetType {
Mesh,
Texture,
Material,
Prefab,
Audio,
Script,
Animation,
Shader,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetEntry {
pub uuid: AssetUuid,
pub path: String,
pub asset_type: AssetType,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, String>,
}
impl AssetEntry {
pub fn new(uuid: AssetUuid, path: impl Into<String>, asset_type: AssetType) -> Self {
Self {
uuid,
path: path.into(),
asset_type,
name: None,
metadata: HashMap::new(),
}
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AssetManifest {
pub version: u32,
pub assets: Vec<AssetEntry>,
#[serde(skip)]
uuid_to_index: HashMap<AssetUuid, usize>,
#[serde(skip)]
path_to_uuid: HashMap<String, AssetUuid>,
#[serde(skip)]
name_to_uuid: HashMap<String, AssetUuid>,
}
impl AssetManifest {
pub const CURRENT_VERSION: u32 = 1;
pub fn new() -> Self {
Self {
version: Self::CURRENT_VERSION,
assets: Vec::new(),
uuid_to_index: HashMap::new(),
path_to_uuid: HashMap::new(),
name_to_uuid: HashMap::new(),
}
}
pub fn add_asset(&mut self, entry: AssetEntry) {
let index = self.assets.len();
let uuid = entry.uuid;
let path = entry.path.clone();
let name = entry.name.clone();
self.uuid_to_index.insert(uuid, index);
self.path_to_uuid.insert(path, uuid);
if let Some(name) = name {
self.name_to_uuid.insert(name, uuid);
}
self.assets.push(entry);
}
pub fn get_by_uuid(&self, uuid: AssetUuid) -> Option<&AssetEntry> {
self.uuid_to_index
.get(&uuid)
.and_then(|index| self.assets.get(*index))
}
pub fn get_by_path(&self, path: &str) -> Option<&AssetEntry> {
self.path_to_uuid
.get(path)
.and_then(|uuid| self.get_by_uuid(*uuid))
}
pub fn get_by_name(&self, name: &str) -> Option<&AssetEntry> {
self.name_to_uuid
.get(name)
.and_then(|uuid| self.get_by_uuid(*uuid))
}
pub fn resolve_uuid(&self, uuid: AssetUuid) -> Option<&str> {
self.get_by_uuid(uuid).map(|entry| entry.path.as_str())
}
pub fn resolve_path(&self, path: &str) -> Option<AssetUuid> {
self.path_to_uuid.get(path).copied()
}
pub fn resolve_name(&self, name: &str) -> Option<AssetUuid> {
self.name_to_uuid.get(name).copied()
}
pub fn assets_of_type(&self, asset_type: AssetType) -> impl Iterator<Item = &AssetEntry> {
self.assets
.iter()
.filter(move |entry| entry.asset_type == asset_type)
}
pub fn rebuild_indices(&mut self) {
self.uuid_to_index.clear();
self.path_to_uuid.clear();
self.name_to_uuid.clear();
for (index, entry) in self.assets.iter().enumerate() {
self.uuid_to_index.insert(entry.uuid, index);
self.path_to_uuid.insert(entry.path.clone(), entry.uuid);
if let Some(name) = &entry.name {
self.name_to_uuid.insert(name.clone(), entry.uuid);
}
}
}
pub fn is_empty(&self) -> bool {
self.assets.is_empty()
}
pub fn len(&self) -> usize {
self.assets.len()
}
}
#[derive(Debug, thiserror::Error)]
pub enum ManifestError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Binary error: {0}")]
Binary(#[from] bincode::Error),
}
pub fn save_manifest_json(manifest: &AssetManifest, path: &Path) -> Result<(), ManifestError> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
serde_json::to_writer_pretty(writer, manifest)?;
Ok(())
}
pub fn load_manifest_json(path: &Path) -> Result<AssetManifest, ManifestError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut manifest: AssetManifest = serde_json::from_reader(reader)?;
manifest.rebuild_indices();
Ok(manifest)
}
pub fn save_manifest_binary(manifest: &AssetManifest, path: &Path) -> Result<(), ManifestError> {
let file = File::create(path)?;
let writer = BufWriter::new(file);
bincode::serialize_into(writer, manifest)?;
Ok(())
}
pub fn load_manifest_binary(path: &Path) -> Result<AssetManifest, ManifestError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
let mut manifest: AssetManifest = bincode::deserialize_from(reader)?;
manifest.rebuild_indices();
Ok(manifest)
}
pub fn save_manifest(manifest: &AssetManifest, path: &Path) -> Result<(), ManifestError> {
match path.extension().and_then(|e| e.to_str()) {
Some("bin") => save_manifest_binary(manifest, path),
_ => save_manifest_json(manifest, path),
}
}
pub fn load_manifest(path: &Path) -> Result<AssetManifest, ManifestError> {
match path.extension().and_then(|e| e.to_str()) {
Some("bin") => load_manifest_binary(path),
_ => load_manifest_json(path),
}
}