use alloc::string::String;
use core::any::{TypeId, type_name};
use bevy_app::App;
use bevy_ecs::resource::Resource;
use bevy_ecs::system::{Local, Query, Res, SystemParam};
use hashbrown::HashSet;
use crate::{
ConfigField, ConfigFieldFor, ConfigNode, Manager, RootNode, SpawnContext, SpawnHandle, manager,
};
pub trait AppExt {
fn init_config<M, C>(&mut self, key: impl Into<String>) -> &mut Self
where
M: Manager + Default,
C: ConfigFieldFor<M>,
C::Metadata: Default,
{
self.init_config_with::<M, C>(key, M::default)
}
fn init_config_with<M, C>(
&mut self,
key: impl Into<String>,
init: impl FnOnce() -> M,
) -> &mut Self
where
M: Manager,
C: ConfigFieldFor<M>,
C::Metadata: Default;
}
#[derive(Resource)]
struct ManagerType {
id: TypeId,
name: &'static str,
root_keys: HashSet<String>,
}
#[derive(Resource)]
struct RootField<C: ConfigField> {
spawn_handle: C::SpawnHandle,
}
impl AppExt for App {
fn init_config_with<M, C>(
&mut self,
key: impl Into<String>,
init: impl FnOnce() -> M,
) -> &mut Self
where
M: Manager,
C: ConfigFieldFor<M>,
C::Metadata: Default,
{
if let Some(&ManagerType { id, name, .. }) = self.world().get_resource() {
assert!(
id == TypeId::of::<M>(),
"Use of multiple different config managers in the same app is not allowed: {name} \
vs {}",
type_name::<M>()
);
} else {
self.insert_resource(ManagerType {
id: TypeId::of::<M>(),
name: type_name::<M>(),
root_keys: HashSet::new(),
});
self.insert_resource(manager::Instance { instance: init() });
}
let key = key.into();
let key_exists = self
.world_mut()
.get_resource_mut::<ManagerType>()
.expect("just checked")
.root_keys
.replace(key.clone());
if let Some(key) = key_exists {
panic!("Cannot reuse config key {key:?} in the same app");
}
assert!(
self.world().get_resource::<RootField<C>>().is_none(),
"Cannot initialize multiple root config fields of the same type in the same app: {}",
type_name::<C>()
);
let spawn_handle = C::spawn_world(
self.world_mut(),
SpawnContext { path: [key].into(), parent: None, dependency: None },
Default::default(),
);
self.world_mut().entity_mut(spawn_handle.node()).insert(RootNode);
self.insert_resource(RootField::<C> { spawn_handle });
self
}
}
#[derive(SystemParam)]
pub struct ReadConfig<'w, 's, C: ConfigField> {
read_query: Query<'w, 's, <C as ConfigField>::ReadQueryData>,
changed_query: Query<'w, 's, (&'static ConfigNode, <C as ConfigField>::ChangedQueryData)>,
root_field: Res<'w, RootField<C>>,
}
impl<C: ConfigField> ReadConfig<'_, '_, C> {
#[must_use]
pub fn read(&self) -> C::Reader<'_> {
C::read_world(&self.read_query, &self.root_field.spawn_handle)
}
#[must_use]
pub fn changed(&self) -> C::Changed {
C::changed(&self.changed_query, &self.root_field.spawn_handle)
}
}
#[derive(SystemParam)]
pub struct ReadConfigChange<'w, 's, C: ConfigField> {
last_value: Local<'s, Option<<C as ConfigField>::Changed>>,
read_config: ReadConfig<'w, 's, C>,
}
impl<C: ConfigField> ReadConfigChange<'_, '_, C> {
#[must_use]
pub fn read(&self) -> C::Reader<'_> { self.read_config.read() }
pub fn consume_change(&mut self) -> bool {
let changed = self.read_config.changed();
if self.last_value.as_ref().is_none_or(|v| *v != changed) {
*self.last_value = Some(changed);
true
} else {
false
}
}
}