use crate::prelude::*;
use beet_core_macros::BundleEffect;
use bevy::ecs::error::ErrorContext;
use bevy::ecs::relationship::RelatedSpawner;
use bevy::ecs::relationship::Relationship;
use bevy::ecs::spawn::SpawnRelatedBundle;
use bevy::ecs::spawn::SpawnWith;
use bevy::ecs::system::IntoObserverSystem;
pub fn spawn_with<T: RelationshipTarget, F>(
func: F,
) -> SpawnRelatedBundle<T::Relationship, SpawnWith<F>>
where
F: 'static + Send + Sync + FnOnce(&mut RelatedSpawner<T::Relationship>),
{
T::spawn(SpawnWith(func))
}
#[derive(BundleEffect)]
pub struct OnSpawn(
pub Box<dyn 'static + Send + Sync + FnOnce(&mut EntityWorldMut)>,
);
pub trait ApplyToEntity<M>: 'static + Send + Sync {
fn apply(self, entity: &mut EntityWorldMut);
}
pub struct BundleApplyToEntityMarker;
pub struct ResultApplyToEntityMarker;
impl<T: Bundle> ApplyToEntity<BundleApplyToEntityMarker> for T {
fn apply(self, entity: &mut EntityWorldMut) { entity.insert(self); }
}
impl<T: Bundle> ApplyToEntity<ResultApplyToEntityMarker> for Result<T> {
fn apply(self, entity: &mut EntityWorldMut) {
match self {
Ok(bundle) => {
entity.insert(bundle);
}
Err(err) => entity.world_scope(|world| {
world.default_error_handler()(
err.into(),
ErrorContext::Command {
name: "ApplyToEntity".into(),
},
);
}),
}
}
}
impl OnSpawn {
pub fn new(
func: impl 'static + Send + Sync + FnOnce(&mut EntityWorldMut),
) -> Self {
Self(Box::new(func))
}
pub fn insert(bundle: impl Bundle) -> Self {
Self::new(move |entity| {
entity.insert(bundle);
})
}
pub fn insert_option(bundle: Option<impl Bundle>) -> Self {
Self::new(move |entity| {
if let Some(bundle) = bundle {
entity.insert(bundle);
}
})
}
pub fn run_insert<
System: 'static + Send + Sync + IntoSystem<(), Out, M1>,
M1,
Out: ApplyToEntity<M2>,
M2,
>(
system: System,
) -> Self {
Self::new(move |entity| {
entity
.world_scope(move |world| world.run_system_once(system))
.unwrap()
.apply(entity);
})
}
pub fn insert_resource(resource: impl Resource) -> Self {
Self::new(move |entity| {
entity.world_scope(move |world| world.insert_resource(resource));
})
}
pub fn trigger<M>(event: impl IntoEntityTargetEvent<M>) -> Self {
Self::new(move |entity| {
entity.trigger_target(event);
})
}
pub fn trigger_option<M>(
event: Option<impl IntoEntityTargetEvent<M>>,
) -> Self {
Self::new(move |entity| {
if let Some(event) = event {
entity.trigger_target(event);
}
})
}
pub fn observe<E: Event, B: Bundle, M>(
observer: impl 'static + Send + Sync + IntoObserverSystem<E, B, M>,
) -> Self {
Self::new(move |entity| {
entity.observe_any(observer);
})
}
fn effect(self, entity: &mut EntityWorldMut) { (self.0)(entity); }
pub fn new_async<Fut, Out>(
func: impl 'static + Send + Sync + FnOnce(AsyncEntity) -> Fut,
) -> Self
where
Fut: 'static + Send + Sync + Future<Output = Out>,
Out: 'static + AsyncTaskOut,
{
Self(Box::new(move |entity| {
let id = entity.id();
entity.world_scope(move |world| {
world
.run_async(async move |world| func(world.entity(id)).await);
});
}))
}
pub fn new_async_local<Fut, Out>(
func: impl 'static + Send + Sync + FnOnce(AsyncEntity) -> Fut,
) -> Self
where
Fut: 'static + Future<Output = Out>,
Out: 'static + AsyncTaskOut,
{
Self(Box::new(move |entity| {
let id = entity.id();
entity.world_scope(move |world| {
world.run_async_local(async move |world| {
func(world.entity(id)).await
});
});
}))
}
}
#[derive(Clone, BundleEffect)]
pub struct OnSpawnTyped<F: 'static + Send + Sync + FnOnce(&mut EntityWorldMut)>(
pub F,
);
impl<F: Send + Sync + FnOnce(&mut EntityWorldMut)> OnSpawnTyped<F> {
pub fn new(func: F) -> Self { Self(func) }
fn effect(self, entity: &mut EntityWorldMut) { self.0(entity); }
}
#[derive(Component)]
pub struct OnSpawnDeferred(
pub Box<dyn 'static + Send + Sync + FnOnce(&mut EntityWorldMut) -> Result>,
);
impl OnSpawnDeferred {
pub fn new(
func: impl 'static + Send + Sync + FnOnce(&mut EntityWorldMut) -> Result,
) -> Self {
Self(Box::new(func))
}
pub fn parent<R: Relationship>(
func: impl 'static + Send + Sync + FnOnce(&mut EntityWorldMut) -> Result,
) -> Self {
Self::new(move |entity| {
let Some(parent) = entity.get::<R>() else {
bevybail!(
"OnSpawnDeferred::insert_parent: Entity does not have a parent"
);
};
let parent = parent.get();
entity.world_scope(move |world| func(&mut world.entity_mut(parent)))
})
}
pub fn insert(bundle: impl Bundle) -> Self {
Self::new(move |entity| {
entity.insert(bundle);
Ok(())
})
}
pub fn insert_parent<R: Relationship>(bundle: impl Bundle) -> Self {
Self::parent::<R>(move |entity| {
entity.insert(bundle);
Ok(())
})
}
pub fn trigger_target<M>(ev: impl IntoEntityTargetEvent<M>) -> Self {
Self::new(move |entity| {
entity.trigger_target(ev);
Ok(())
})
}
pub fn flush(
mut commands: Commands,
mut query: Query<(Entity, &mut Self)>,
) {
for (entity, mut on_spawn) in query.iter_mut() {
commands.entity(entity).remove::<Self>();
let func = on_spawn.take();
commands.queue(move |world: &mut World| {
let mut entity = world.entity_mut(entity);
func.call(&mut entity)
});
}
}
pub fn into_command(
self,
entity: Entity,
) -> impl FnOnce(&mut World) -> Result {
move |world: &mut World| {
let mut entity = world.entity_mut(entity);
self.call(&mut entity)
}
}
pub fn call(self, entity: &mut EntityWorldMut) -> Result {
(self.0)(entity)
}
pub fn take(&mut self) -> Self {
Self::new(std::mem::replace(
&mut self.0,
Box::new(|_| {
panic!("OnSpawwnDeferred: This method has already been taken")
}),
))
}
}
#[derive(BundleEffect)]
pub struct OnSpawnClone(pub Box<dyn CloneEntityFunc>);
impl OnSpawnClone {
pub fn new(func: impl CloneEntityFunc) -> Self { Self(Box::new(func)) }
pub fn insert<F, O>(func: F) -> Self
where
F: 'static + Send + Sync + Clone + FnOnce() -> O,
O: Bundle,
{
Self::new(move |entity| {
entity.insert(func.clone()());
})
}
fn effect(self, entity: &mut EntityWorldMut) { (self.0)(entity); }
}
impl Clone for OnSpawnClone {
fn clone(&self) -> Self { Self(self.0.box_clone()) }
}
pub trait CloneEntityFunc:
'static + Send + Sync + Fn(&mut EntityWorldMut)
{
fn box_clone(&self) -> Box<dyn CloneEntityFunc>;
}
impl<T> CloneEntityFunc for T
where
T: 'static + Send + Sync + Clone + Fn(&mut EntityWorldMut),
{
fn box_clone(&self) -> Box<dyn CloneEntityFunc> { Box::new(self.clone()) }
}
#[cfg(test)]
mod test {
use crate::prelude::*;
#[test]
fn dfs() {
let mut world = World::new();
let numbers = Store::default();
world.spawn((
OnSpawnTyped::new(move |entity_world_mut| {
numbers.push(1);
entity_world_mut.insert(OnSpawnTyped::new(move |_| {
numbers.push(2);
}));
}),
OnSpawnTyped::new(move |_| {
numbers.push(3);
}),
));
numbers.get().xpect_eq(&[1, 2, 3]);
}
#[test]
fn on_spawn_deferred() {
let mut world = World::new();
let numbers = Store::default();
world.spawn((
OnSpawnDeferred::new(move |entity_world_mut| {
numbers.push(1);
entity_world_mut.insert(OnSpawnTyped::new(move |_| {
numbers.push(2);
}));
Ok(())
}),
children![OnSpawnDeferred::new(move |_| {
numbers.push(3);
Ok(())
}),],
));
numbers.get().xpect_eq(&[] as &[u32]);
world.run_system_cached(OnSpawnDeferred::flush).unwrap();
#[cfg(target_arch = "wasm32")]
numbers.get().xpect_eq(&[1, 2, 3]);
#[cfg(not(target_arch = "wasm32"))]
numbers.get().xpect_eq(&[3, 1, 2]);
}
#[test]
fn observe() {
#[derive(EntityEvent)]
struct Foo(Entity);
let store = Store::default();
let mut world = World::new();
world
.spawn(OnSpawn::observe(move |_: On<Foo>| store.set(3)))
.trigger(Foo);
store.get().xpect_eq(3);
}
}