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.
109 lines
3.4 KiB
109 lines
3.4 KiB
use std::marker::PhantomData; |
|
|
|
use bevy::prelude::*; |
|
|
|
use bevy::asset::io::Reader; |
|
use bevy::asset::{AssetLoader, AssetPath, LoadContext}; |
|
use bevy::ecs::system::SystemParam; |
|
use derive_where::derive_where; |
|
|
|
use crate::assets::asset_tracking::LoadResource; |
|
use crate::identifier::{Identifier, IdentifierMap}; |
|
|
|
/// Trait implemented by [`Asset`]s which can appear in a [`Manifest`]. |
|
pub trait ManifestEntry: Asset { |
|
type Marker: TypePath; |
|
} |
|
|
|
#[derive(Resource, Asset, Reflect)] |
|
#[derive_where(Clone)] |
|
struct ManifestResource<T: ManifestEntry> { |
|
#[dependency] |
|
handle: Handle<ManifestAsset<T>>, |
|
} |
|
|
|
#[derive(Asset, TypePath)] |
|
struct ManifestAsset<T: ManifestEntry> { |
|
#[dependency] |
|
map: IdentifierMap<T::Marker, Handle<T>>, |
|
} |
|
|
|
impl<T: ManifestEntry> ManifestAsset<T> { |
|
pub fn get(&self, id: &Identifier<T::Marker>) -> Option<&Handle<T>> { |
|
self.map.get(id) |
|
} |
|
} |
|
|
|
#[derive_where(Default)] |
|
struct ManifestAssetLoader<T: ManifestEntry> { |
|
entry_type: PhantomData<T>, |
|
} |
|
|
|
impl<T: ManifestEntry> AssetLoader for ManifestAssetLoader<T> { |
|
type Asset = ManifestAsset<T>; |
|
type Settings = (); |
|
type Error = BevyError; |
|
|
|
async fn load( |
|
&self, |
|
reader: &mut dyn Reader, |
|
_settings: &Self::Settings, |
|
load_context: &mut LoadContext<'_>, |
|
) -> Result<Self::Asset, Self::Error> { |
|
let mut bytes = Vec::new(); |
|
reader.read_to_end(&mut bytes).await?; |
|
let raw = ron::de::from_bytes::<Vec<String>>(&bytes)?; |
|
|
|
let mut map = IdentifierMap::default(); |
|
for id in raw { |
|
let id = Identifier::<T::Marker>::new(id)?; |
|
let handle = load_context.load(format!("blocks/{id}.ron")); |
|
map.try_insert(id, handle).map_err(|_| "duplicate entry")?; |
|
} |
|
Ok(ManifestAsset { map }) |
|
} |
|
|
|
fn extensions(&self) -> &[&str] { |
|
&["manifest.ron"] |
|
} |
|
} |
|
|
|
#[derive(SystemParam)] |
|
pub struct Manifest<'w, T: ManifestEntry> { |
|
resource: Option<Res<'w, ManifestResource<T>>>, |
|
manifest_assets: Res<'w, Assets<ManifestAsset<T>>>, |
|
entry_assets: Res<'w, Assets<T>>, |
|
} |
|
|
|
impl<T: ManifestEntry> Manifest<'_, T> { |
|
/// Looks up an [`Asset`] of type `T` from its associated manifest. |
|
/// |
|
/// Returns `None` if an asset with the given identifier is missing from |
|
/// the manifest, or if the manifest is not loaded, including if it has |
|
/// not been initialized with [`InitManifest::init_manifest`]. |
|
pub fn get(&self, id: &Identifier<T::Marker>) -> Option<&T> { |
|
let manifest_handle = &self.resource.as_ref()?.handle; |
|
// SAFETY: Manifest loads this as a dependency, so it should exist. |
|
let manifest = self.manifest_assets.get(manifest_handle).unwrap(); |
|
let entry_handle = manifest.get(id)?; |
|
// SAFETY: Entry loads this as a dependency, so it should exist. |
|
let entry = self.entry_assets.get(entry_handle).unwrap(); |
|
Some(entry) |
|
} |
|
} |
|
|
|
pub trait InitManifest { |
|
fn init_manifest<'a, T: ManifestEntry>(&mut self, path: impl Into<AssetPath<'a>>); |
|
} |
|
|
|
impl InitManifest for App { |
|
fn init_manifest<'a, T: ManifestEntry>(&mut self, path: impl Into<AssetPath<'a>>) { |
|
self.init_asset::<T>(); |
|
self.init_asset::<ManifestAsset<T>>(); |
|
self.init_asset_loader::<ManifestAssetLoader<T>>(); |
|
|
|
// Insert a `Resource` that holds a handle to the `ManifestAsset` once it's loaded. |
|
let handle = self.world_mut().load_asset(path); |
|
self.load_resource_with(ManifestResource::<T> { handle }); |
|
} |
|
}
|
|
|