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 { #[dependency] handle: Handle>, } #[derive(Asset, TypePath)] struct ManifestAsset { #[dependency] map: IdentifierMap>, } impl ManifestAsset { pub fn get(&self, id: &Identifier) -> Option<&Handle> { self.map.get(id) } } #[derive_where(Default)] struct ManifestAssetLoader { entry_type: PhantomData, } impl AssetLoader for ManifestAssetLoader { type Asset = ManifestAsset; type Settings = (); type Error = BevyError; async fn load( &self, reader: &mut dyn Reader, _settings: &Self::Settings, load_context: &mut LoadContext<'_>, ) -> Result { let mut bytes = Vec::new(); reader.read_to_end(&mut bytes).await?; let raw = ron::de::from_bytes::>(&bytes)?; let mut map = IdentifierMap::default(); for id in raw { let id = Identifier::::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>>, manifest_assets: Res<'w, Assets>>, entry_assets: Res<'w, Assets>, } impl 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) -> 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>); } impl InitManifest for App { fn init_manifest<'a, T: ManifestEntry>(&mut self, path: impl Into>) { self.init_asset::(); self.init_asset::>(); self.init_asset_loader::>(); // 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:: { handle }); } }