use std::marker::PhantomData;
use bevy_ecs::component::Mutable;
use bevy_ecs::lifecycle::HookContext;
use bevy_ecs::prelude::*;
use bevy_ecs::world::DeferredWorld;
use crate::Static;
pub trait MergeComponent: Component<Mutability = Mutable> {
fn merge(&mut self, other: Self);
}
#[derive(Component)]
#[component(on_insert = Self::on_insert)]
pub struct Merge<T: MergeComponent>(pub T);
impl<T: MergeComponent> Merge<T> {
pub fn with<F: Static + FnOnce() -> T>(f: F) -> MergeWith<T, impl Static + FnOnce() -> T> {
MergeWith::new(f)
}
fn on_insert(mut world: DeferredWorld, ctx: HookContext) {
world
.commands()
.entity(ctx.entity)
.queue(|mut entity: EntityWorldMut| {
entity.take::<Self>().unwrap().apply(entity);
});
}
}
impl<T: MergeComponent> From<T> for Merge<T> {
fn from(value: T) -> Self {
Self(value)
}
}
impl<T: MergeComponent> EntityCommand for Merge<T> {
fn apply(self, mut entity: EntityWorldMut) {
let Self(source) = self;
if let Some(mut target) = entity.get_mut::<T>() {
target.merge(source);
} else {
entity.insert(source);
}
}
}
#[derive(Component)]
#[component(on_insert = Self::on_insert)]
pub struct MergeFrom<M: Static, T: MergeComponent>(Merge<T>, PhantomData<M>);
impl<M: Static, T: MergeComponent> MergeFrom<M, T> {
fn on_insert(mut world: DeferredWorld, ctx: HookContext) {
world
.commands()
.entity(ctx.entity)
.queue(|mut entity: EntityWorldMut| {
let Self(inner, ..) = entity.take::<Self>().unwrap();
inner.apply(entity);
});
}
}
impl<M: Static, T: MergeComponent> From<T> for MergeFrom<M, T> {
fn from(value: T) -> Self {
Self(Merge(value), PhantomData)
}
}
#[derive(Component)]
#[component(on_insert = Self::on_insert)]
pub struct MergeWith<T: MergeComponent, F: Static + FnOnce() -> T>(F, PhantomData<T>);
impl<F: Static + FnOnce() -> T, T: MergeComponent> MergeWith<T, F> {
pub fn new(f: F) -> Self {
Self(f, PhantomData)
}
fn on_insert(mut world: DeferredWorld, ctx: HookContext) {
world
.commands()
.entity(ctx.entity)
.queue(|mut entity: EntityWorldMut| {
let Self(f, ..) = entity.take::<Self>().unwrap();
Merge(f()).apply(entity);
});
}
}
impl<F: Static + FnOnce() -> T, T: MergeComponent> From<F> for MergeWith<T, F> {
fn from(f: F) -> Self {
Self::new(f)
}
}
#[macro_export]
macro_rules! relationship {
{
$(#[$target_attr:meta])*
$target_vis:vis $target:ident($(#[$target_inner_attr:meta])* $target_inner_vis:vis $target_inner:ty)
-> {
$(#[$source_attr:meta])*
$source_vis:vis $source:ident($(#[$source_inner_attr:meta])* $source_inner_vis:vis $source_inner:ty)
}
} => {
relationship! {
$(#[$target_attr])* $target_vis $target($target_inner_vis $target_inner) -> [] {
$(#[$source_attr])* $source_vis $source($source_inner_vis $source_inner)
}
}
};
{
$(#[$target_attr:meta])*
$target_vis:vis $target:ident($(#[$target_inner_attr:meta])* $target_inner_vis:vis $target_inner:ty)
-> [$($options:expr),*] {
$(#[$source_attr:meta])*
$source_vis:vis $source:ident($(#[$source_inner_attr:meta])* $source_inner_vis:vis $source_inner:ty)
}
} => {
$(#[$target_attr])*
#[derive(Component)]
#[relationship_target(relationship = $source, $($options),*)]
$target_vis struct $target($(#[$target_inner_attr])* $target_inner_vis $target_inner);
$(#[$source_attr])*
#[derive(Component)]
#[relationship(relationship_target = $target)]
$source_vis struct $source($(#[$source_inner_attr])* $source_inner_vis $source_inner);
};
}
#[test]
fn test_merge_component() {
#[derive(Component, Default)]
struct N(usize);
impl MergeComponent for N {
fn merge(&mut self, other: Self) {
self.0 += other.0;
}
}
#[derive(Component, Default)]
#[require(MergeFrom<Self, N> = N(1))]
struct A;
#[derive(Component, Default)]
#[require(A, MergeFrom<Self, N> = N(2))]
struct B;
#[derive(Component, Default)]
#[require(A, B)]
struct C;
let mut w = World::new();
let e = w.spawn(C);
let &N(v) = e.get().unwrap();
assert_eq!(v, 3);
}
#[test]
fn test_relationship_linked_spawn() {
relationship! {
pub Owner(Vec<Entity>) -> [linked_spawn] {
pub OwnedBy(pub Entity)
}
}
let mut w = World::new();
let a = w.spawn_empty().id();
let b = w.spawn(OwnedBy(a)).id();
assert_eq!(w.get::<Owner>(a).unwrap().0[0], b);
assert!(w.entities().contains(b));
w.entity_mut(a).despawn();
assert!(!w.entities().contains(b));
}