use bevy::{
asset::{Asset, Assets, AssetApp, AssetEvent, AssetLoader, AsyncReadExt, AssetServer, LoadContext, io::Reader, Handle},
app::{Startup, Update, App, Plugin},
ecs::{
system::{Commands, Resource},
change_detection::{Res, ResMut},
event::EventReader
},
};
use std::marker::PhantomData;
use ron::de::from_bytes;
use thiserror::Error;
#[derive(Resource)]
pub struct ConfigFileHolder<A>
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
path: &'static str,
handle: Option<Handle<A>>,
_marker: PhantomData<A>
}
pub struct EasyConfigPlugin<A>
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
path: &'static str,
_marker: PhantomData<A>
}
impl<A> EasyConfigPlugin<A>
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
pub fn new(path: &'static str) -> Self {
Self {
path: path,
_marker: PhantomData
}
}
}
impl<A> Plugin for EasyConfigPlugin<A>
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
fn build(&self, app: &mut App) {
app
.init_asset::<A>()
.register_asset_loader(ConfigFileAssetLoader::<A> {
_marker: PhantomData
})
.insert_resource(ConfigFileHolder::<A> {
path: self.path,
handle: None,
_marker: PhantomData
})
.init_resource::<A>()
.add_systems(Startup, add_asset_to_config_file_holder::<A>)
.add_systems(Update, update_resource::<A>);
}
}
fn add_asset_to_config_file_holder<A>(
mut config_file_holder: ResMut<ConfigFileHolder<A>>,
asset_server: Res<AssetServer>,
)
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
let config_file_handle: Handle<A> = asset_server.load(config_file_holder.path);
config_file_holder.handle = Some(config_file_handle);
}
fn update_resource<A>(
mut ev_asset: EventReader<AssetEvent<A>>,
config_files: Res<Assets<A>>,
mut commands: Commands
)
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
for ev in ev_asset.read() {
match ev {
AssetEvent::LoadedWithDependencies { id } => {
let config_file = config_files.get(id.clone());
match config_file {
Some(config_file) => commands.insert_resource(config_file.clone()),
None => println!("Oh nosies 1")
}
},
AssetEvent::Modified { id } => {
let config_file = config_files.get(id.clone());
match config_file {
Some(config_file) => commands.insert_resource(config_file.clone()),
None => println!("Oh nosies 2")
}
},
_ => {}
}
}
}
pub struct ConfigFileAssetLoader<A>
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
_marker: PhantomData<A>,
}
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ConfigFileLoaderError {
#[error("Could not read the file: {0}")]
Io(#[from] std::io::Error),
#[error("Could not parse RON: {0}")]
RonError(#[from] ron::error::SpannedError),
}
impl<A> AssetLoader for ConfigFileAssetLoader<A>
where
for<'de> A: serde::Deserialize<'de> + Asset + Resource + Clone + Default
{
type Asset = A;
type Settings = ();
type Error = ConfigFileLoaderError;
async fn load<'a>(
&'a self,
reader: &'a mut Reader<'_>,
_settings: &'a (),
_load_context: &'a mut LoadContext<'_>,
) -> Result<Self::Asset, Self::Error> {
let mut bytes = Vec::new();
reader.read_to_end(&mut bytes).await?;
let asset = from_bytes::<A>(&bytes)?;
Ok(asset)
}
fn extensions(&self) -> &[&str] {
&["ron"]
}
}