use bevy::asset::io::Reader;
use bevy::asset::{AssetLoader, LoadContext};
use bevy::prelude::*;
use crate::config::CacheConfig;
use crate::manifest::CacheManifest;
#[derive(Asset, TypePath, Clone)]
pub struct CacheManifestAsset(pub CacheManifest);
#[derive(Resource, Default)]
pub(crate) struct ManifestReloadState {
pub(crate) handle: Option<Handle<CacheManifestAsset>>,
pub(crate) skip_next_save: bool,
}
#[derive(Default, TypePath)]
pub struct CacheManifestLoader;
impl AssetLoader for CacheManifestLoader {
type Asset = CacheManifestAsset;
type Settings = ();
type Error = Box<dyn std::error::Error + Send + Sync>;
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 manifest: CacheManifest = ron::from_str(std::str::from_utf8(&bytes)?)?;
Ok(CacheManifestAsset(manifest))
}
fn extensions(&self) -> &[&str] {
&["cache_manifest"]
}
}
pub(crate) fn startup_watch_manifest(
mut state: ResMut<ManifestReloadState>,
asset_server: Res<AssetServer>,
config: Res<CacheConfig>,
) {
let path = config.manifest_fs_path();
if !path.exists() {
let _ = config.ensure_cache_dir();
let pretty = ron::ser::PrettyConfig::default();
let empty = ron::ser::to_string_pretty(&CacheManifest::default(), pretty)
.expect("serialize empty manifest");
let _ = std::fs::write(&path, empty);
}
let handle = asset_server
.load::<CacheManifestAsset>(format!("cache://{}", config.manifest_file_name()));
state.handle = Some(handle);
}
pub(crate) fn sync_manifest_from_asset(
mut manifest: ResMut<CacheManifest>,
mut state: ResMut<ManifestReloadState>,
assets: Res<Assets<CacheManifestAsset>>,
mut events: MessageReader<AssetEvent<CacheManifestAsset>>,
) {
let Some(handle) = state.handle.clone() else {
return;
};
for event in events.read() {
let AssetEvent::Modified { id } = event else {
continue;
};
if *id != handle.id() {
continue;
}
if let Some(asset) = assets.get(&handle) {
if *manifest != asset.0 {
tracing::info!("Cache manifest hot-reloaded from disk.");
state.skip_next_save = true;
*manifest = asset.0.clone();
}
}
}
}
pub(crate) fn save_manifest_skip_reload(
config: Res<CacheConfig>,
manifest: Res<CacheManifest>,
mut state: ResMut<ManifestReloadState>,
) {
if state.skip_next_save {
state.skip_next_save = false;
return;
}
if !manifest.is_changed() {
return;
}
if let Err(e) = manifest.save_to_disk(config.as_ref()) {
tracing::error!("Failed to save cache manifest: {e}");
}
}