use bevy::ecs::component::Mutable;
use bevy::ecs::query::QueryFilter;
use bevy::ecs::schedule::{InternedScheduleLabel, ScheduleLabel};
use bevy::prelude::*;
use crate::attributes::Attributes;
use crate::attributes_mut::AttributesMut;
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct WriteBackSet;
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct AttributeDerivedSet;
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct InitFromSet;
pub trait AttributeDerived: Component<Mutability = Mutable> {
fn should_update(&self, attrs: &Attributes) -> bool;
fn update_from_attributes(&mut self, attrs: &Attributes);
}
pub trait InitFrom: Component<Mutability = Mutable> {
fn init_from_attributes(&mut self, attrs: &Attributes);
}
pub trait InitTo: Component {
fn init_to_attributes<F: QueryFilter>(&self, entity: Entity, attributes: &mut AttributesMut<'_, '_, F>);
}
pub trait WriteBack: Component {
fn should_write_back(&self, attrs: &Attributes) -> bool;
fn write_back<F: QueryFilter>(&self, entity: Entity, attributes: &mut AttributesMut<'_, '_, F>);
}
pub fn update_attribute_derived<T: AttributeDerived>(
mut query: Query<(&mut T, &Attributes), Changed<Attributes>>,
) {
for (mut derived, attrs) in &mut query {
if derived.should_update(attrs) {
derived.update_from_attributes(attrs);
}
}
}
pub fn update_write_back<T: WriteBack>(
q_wb: Query<(Entity, &T), Changed<T>>,
mut attributes: AttributesMut,
) {
for (entity, wb) in &q_wb {
let should = {
let Some(attrs) = attributes.get_attributes(entity) else {
continue;
};
wb.should_write_back(attrs)
};
if should {
wb.write_back(entity, &mut attributes);
}
}
}
pub fn observe_init_to<T: InitTo>(
trigger: On<Add, T>,
query: Query<&T>,
mut attributes: AttributesMut,
) {
let entity = trigger.entity;
if let Ok(component) = query.get(entity) {
component.init_to_attributes(entity, &mut attributes);
}
}
pub fn apply_init_from<T: InitFrom>(
mut query: Query<(&mut T, &Attributes), Added<T>>,
) {
for (mut component, attrs) in &mut query {
component.init_from_attributes(attrs);
}
}
pub trait AttributesAppExt {
fn register_attribute_derived<T: AttributeDerived>(&mut self) -> &mut Self;
fn register_write_back<T: WriteBack>(&mut self) -> &mut Self;
fn register_init_to<T: InitTo>(&mut self) -> &mut Self;
fn register_init_from<T: InitFrom>(&mut self) -> &mut Self;
}
impl AttributesAppExt for App {
fn register_attribute_derived<T: AttributeDerived>(&mut self) -> &mut Self {
self.add_systems(
PreUpdate,
update_attribute_derived::<T>.in_set(AttributeDerivedSet),
)
.add_systems(
PostUpdate,
update_attribute_derived::<T>.in_set(AttributeDerivedSet),
)
}
fn register_write_back<T: WriteBack>(&mut self) -> &mut Self {
self.add_systems(
PreUpdate,
update_write_back::<T>.in_set(WriteBackSet),
)
.add_systems(
PostUpdate,
update_write_back::<T>.in_set(WriteBackSet),
)
}
fn register_init_to<T: InitTo>(&mut self) -> &mut Self {
self.add_observer(observe_init_to::<T>);
self
}
fn register_init_from<T: InitFrom>(&mut self) -> &mut Self {
self.add_systems(
PreUpdate,
apply_init_from::<T>.in_set(InitFromSet),
)
}
}
pub struct AttributeRegistration {
pub register_fn: fn(&mut App),
pub register_in_schedule_fn: Option<fn(&mut App, InternedScheduleLabel)>,
}
inventory::collect!(AttributeRegistration);
pub fn add_gauge_sync_to_schedule(app: &mut App, schedule: impl ScheduleLabel + Clone) {
let schedule = schedule.intern();
app.configure_sets(schedule, (WriteBackSet, AttributeDerivedSet).chain());
for reg in inventory::iter::<AttributeRegistration> {
if let Some(register) = reg.register_in_schedule_fn {
register(app, schedule);
}
}
}
#[macro_export]
macro_rules! register_derived {
($ty:ty) => {
$crate::_register_attribute!(attribute_derived, $ty);
};
}
#[macro_export]
macro_rules! register_write_back {
($ty:ty) => {
$crate::_register_attribute!(write_back, $ty);
};
}
#[macro_export]
macro_rules! register_init_to {
($ty:ty) => {
$crate::_register_attribute!(init_to, $ty);
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! _register_attribute {
(attribute_derived, $ty:ty) => {
$crate::inventory::submit! {
$crate::derived::AttributeRegistration {
register_fn: |app| {
use $crate::derived::AttributesAppExt;
app.register_attribute_derived::<$ty>();
},
register_in_schedule_fn: Some(|app, schedule| {
use ::bevy::prelude::*;
app.add_systems(
schedule,
$crate::derived::update_attribute_derived::<$ty>
.in_set($crate::derived::AttributeDerivedSet),
);
}),
}
}
};
(write_back, $ty:ty) => {
$crate::inventory::submit! {
$crate::derived::AttributeRegistration {
register_fn: |app| {
use $crate::derived::AttributesAppExt;
app.register_write_back::<$ty>();
},
register_in_schedule_fn: Some(|app, schedule| {
use ::bevy::prelude::*;
app.add_systems(
schedule,
$crate::derived::update_write_back::<$ty>
.in_set($crate::derived::WriteBackSet),
);
}),
}
}
};
(init_to, $ty:ty) => {
$crate::inventory::submit! {
$crate::derived::AttributeRegistration {
register_fn: |app| {
use $crate::derived::AttributesAppExt;
app.register_init_to::<$ty>();
},
register_in_schedule_fn: None,
}
}
};
(init_from, $ty:ty) => {
$crate::inventory::submit! {
$crate::derived::AttributeRegistration {
register_fn: |app| {
use $crate::derived::AttributesAppExt;
app.register_init_from::<$ty>();
},
register_in_schedule_fn: Some(|app, schedule| {
use ::bevy::prelude::*;
app.add_systems(
schedule,
$crate::derived::apply_init_from::<$ty>
.in_set($crate::derived::InitFromSet),
);
}),
}
}
};
}