Bloxel sandbox game similar to Minecraft "Classic" (2009) written in Rust with Bevy
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

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 });
}
}