#![no_std]
#![warn(missing_docs, clippy::pedantic)]
#![allow(clippy::missing_panics_doc)]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::num::NonZeroU64;
use bevy_ecs::component::Component;
use bevy_ecs::entity::Entity;
use bevy_ecs::query::QueryData;
use bevy_ecs::world::{EntityRef, EntityWorldMut, World};
pub mod impls;
pub use impls::BareField;
mod query;
pub use query::QueryLike;
mod enum_;
pub use enum_::{EnumDiscriminant, EnumDiscriminantMetadata, EnumDiscriminantWrapper};
pub mod manager;
pub use manager::Manager;
pub mod __import;
mod macro_doc;
pub use macro_doc::Config;
mod app;
pub use app::{AppExt, ReadConfig, ReadConfigChange};
mod tree;
pub use tree::{
ChildNodeList, ChildNodeOf, ConditionalRelevance, ConfigNode, RootNode, ScalarField,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct FieldGeneration(NonZeroU64);
impl Default for FieldGeneration {
fn default() -> Self { FieldGeneration(const { NonZeroU64::new(1).unwrap() }) }
}
impl FieldGeneration {
#[must_use]
pub fn next(self) -> Self {
FieldGeneration(self.0.checked_add(1).expect("field generation overflow"))
}
}
#[derive(Clone)]
pub struct SpawnContext {
pub path: Vec<String>,
pub parent: Option<Entity>,
pub dependency: Option<ConditionalRelevance>,
}
impl SpawnContext {
#[must_use]
pub fn join(
&self,
key: impl IntoIterator<Item = impl Into<String>>,
parent: Option<Entity>,
) -> Self {
SpawnContext {
path: self
.path
.iter()
.cloned()
.chain(key.into_iter().map(Into::<String>::into))
.collect(),
parent,
dependency: None,
}
}
#[must_use]
pub fn with_dependency(
mut self,
dependency: Entity,
is_entity_relevant: fn(EntityRef) -> bool,
) -> Self {
self.dependency = Some(ConditionalRelevance { dependency, is_entity_relevant });
self
}
}
pub trait SpawnHandle {
fn node(&self) -> Entity;
}
impl SpawnHandle for Entity {
fn node(&self) -> Entity { *self }
}
pub trait ConfigField: 'static {
type SpawnHandle: SpawnHandle + 'static + Send + Sync;
type Reader<'a>;
type ReadQueryData: QueryData;
type Metadata: Default + 'static + Send + Sync;
type Changed: Clone + Eq + 'static + Send + Sync;
type ChangedQueryData: QueryData;
fn read_world<'a, 's>(
query: impl QueryLike<
Item = <<Self::ReadQueryData as QueryData>::ReadOnly as QueryData>::Item<'a, 's>,
>,
spawn_handle: &Self::SpawnHandle,
) -> Self::Reader<'a>;
fn changed<'a, 's>(
query: impl QueryLike<
Item = (
&'a ConfigNode,
<<Self::ChangedQueryData as QueryData>::ReadOnly as QueryData>::Item<'a, 's>,
),
>,
spawn_handle: &Self::SpawnHandle,
) -> Self::Changed;
}
pub trait ConfigFieldFor<M>: ConfigField {
fn spawn_world(
world: &mut World,
ctx: SpawnContext,
metadata: Self::Metadata,
) -> Self::SpawnHandle;
}
#[derive(Component)]
pub struct ScalarData<T>(pub T);
#[derive(Component)]
pub struct ScalarMetadata<T: ConfigField>(pub T::Metadata);
#[macro_export]
macro_rules! impl_scalar_config_field {
($ty:ty, $metadata:ty, $default_from_metadata:expr, $lt:lifetime => $mapped_ty:ty, $map_fn:expr $(,)?) => {
impl $crate::ConfigField for $ty {
type SpawnHandle = $crate::__import::Entity;
type Reader<$lt> = $mapped_ty;
type ReadQueryData = $crate::__import::Option<&'static $crate::ScalarData<Self>>;
type Metadata = $metadata;
type Changed = $crate::FieldGeneration;
type ChangedQueryData = ();
fn read_world<'a, 's>(
query: impl $crate::QueryLike<Item = <<Self::ReadQueryData as $crate::__import::QueryData>::ReadOnly as $crate::__import::QueryData>::Item<'a, 's>>,
&spawn_handle: &$crate::__import::Entity,
) -> Self::Reader<'a> {
let data = query.get(spawn_handle).expect(
"entity managed by config field must remain active as long as the config \
handle is used",
);
$map_fn(&data.as_ref().expect("scalar data component must remain valid with Self type").0)
}
fn changed<'a, 's>(
query: impl $crate::QueryLike<Item = (&'a $crate::ConfigNode, <<Self::ChangedQueryData as $crate::__import::QueryData>::ReadOnly as $crate::__import::QueryData>::Item<'a, 's>)>,
&spawn_handle: &$crate::__import::Entity,
) -> Self::Changed {
let entity = query.get(spawn_handle).expect(
"entity managed by config field must remain active as long as the config \
handle is used",
);
entity.0.generation
}
}
impl<M: $crate::manager::Supports<$ty>> $crate::ConfigFieldFor<M> for $ty {
fn spawn_world(
world: &mut $crate::__import::World,
ctx: $crate::SpawnContext,
metadata: Self::Metadata,
) -> $crate::__import::Entity {
let manager_comps =
world.resource_mut::<$crate::manager::Instance<M>>().new_entity::<$ty>();
let mut entity = world.spawn((
$crate::ScalarData::<Self>($default_from_metadata(&metadata)),
$crate::ScalarMetadata::<Self>(metadata),
manager_comps,
));
$crate::init_config_node(&mut entity, ctx);
entity.id()
}
}
};
}
use impl_scalar_config_field as impl_scalar_config_field_;
pub fn init_config_node(entity: &mut EntityWorldMut, ctx: SpawnContext) {
entity.insert(ConfigNode { path: ctx.path, generation: FieldGeneration::default() });
if let Some(parent) = ctx.parent {
entity.insert(ChildNodeOf(parent));
}
if let Some(dependency) = ctx.dependency {
entity.insert(dependency);
}
}
#[derive(Default, Clone)]
pub struct StructMetadata;