use std::any::TypeId;
use std::marker::PhantomData;
use bevy::ecs::lifecycle::HookContext;
use bevy::ecs::system::EntityCommands;
use bevy::ecs::world::DeferredWorld;
use bevy::prelude::*;
use bevy_cobweb::prelude::*;
use crate::prelude::*;
fn register_reactor<Triggers: ReactionTriggerBundle>(
c: &mut Commands,
entity: Entity,
syscommand: SystemCommand,
triggers: Triggers,
)
{
let revoke_token = c
.react()
.with(triggers, syscommand, ReactorMode::Revokable)
.unwrap();
c.entity(*syscommand)
.insert((ReactorAttachedTo(entity), RevokeTokenCache(revoke_token)));
}
#[cfg(feature = "hot_reload")]
fn register_update_on_reactor<Triggers: ReactionTriggerBundle>(
In((entity, syscommand, triggers)): In<(Entity, SystemCommand, Triggers)>,
mut c: Commands,
loaded: Query<(), With<HasLoadables>>,
)
{
let is_loaded = loaded.contains(entity);
if !is_loaded && (TypeId::of::<Triggers>() == TypeId::of::<()>()) {
c.queue(syscommand);
c.queue(move |world: &mut World| {
world.despawn(*syscommand);
});
return;
}
let revoke_token = if is_loaded {
let triggers = (triggers, entity_event::<SceneNodeBuilt>(entity));
c.react()
.with(triggers, syscommand, ReactorMode::Revokable)
.unwrap()
} else {
c.react()
.with(triggers, syscommand, ReactorMode::Revokable)
.unwrap()
};
c.entity(*syscommand)
.insert((ReactorAttachedTo(entity), RevokeTokenCache(revoke_token)));
c.queue(syscommand);
}
#[cfg(not(feature = "hot_reload"))]
fn register_update_on_reactor<Triggers: ReactionTriggerBundle>(
c: &mut Commands,
entity: Entity,
syscommand: SystemCommand,
triggers: Triggers,
)
{
if TypeId::of::<Triggers>() == TypeId::of::<()>() {
c.queue(syscommand);
c.queue(move |world: &mut World| {
world.despawn(*syscommand);
});
return;
}
let revoke_token = c
.react()
.with(triggers, syscommand, ReactorMode::Revokable)
.unwrap();
c.entity(*syscommand)
.insert((ReactorAttachedTo(entity), RevokeTokenCache(revoke_token)));
c.queue(syscommand);
}
#[derive(Component)]
struct RevokeTokenCache(RevokeToken);
#[derive(Component)]
#[relationship(relationship_target = AttachedReactors)]
struct ReactorAttachedTo(Entity);
#[derive(Component)]
#[relationship_target(relationship = ReactorAttachedTo)]
#[component(on_despawn = revoke_tokens)]
struct AttachedReactors(Vec<Entity>);
fn revoke_tokens(mut w: DeferredWorld, context: HookContext)
{
let (entities, mut c) = w.entities_and_commands();
let reactors = entities
.get(context.entity)
.unwrap()
.get::<AttachedReactors>()
.unwrap();
for reactor in reactors.iter() {
let Ok(entity) = entities.get(reactor) else { continue };
let Some(cache) = entity.get::<RevokeTokenCache>() else { continue };
c.react().revoke(cache.0.clone());
}
}
pub struct OnEventExt<'a, T: Send + Sync + 'static>
{
c: Commands<'a, 'a>,
entity: Entity,
_p: PhantomData<T>,
}
impl<'a, T: Send + Sync + 'static> OnEventExt<'a, T>
{
pub(crate) fn new(c: Commands<'a, 'a>, entity: Entity) -> OnEventExt<'a, T>
{
Self { c, entity, _p: PhantomData }
}
pub fn r<R: CobwebResult, M>(mut self, callback: impl IntoSystem<(), R, M> + Send + Sync + 'static)
{
self.c.react().on(entity_event::<T>(self.entity), callback);
}
}
#[derive(Debug, Copy, Clone)]
pub struct TargetId(pub Entity);
impl SystemInput for TargetId
{
type Param<'i> = TargetId;
type Inner<'i> = Entity;
fn wrap(this: Self::Inner<'_>) -> Self::Param<'_>
{
TargetId(this)
}
}
impl Deref for TargetId
{
type Target = Entity;
fn deref(&self) -> &Self::Target
{
&self.0
}
}
impl DerefMut for TargetId
{
fn deref_mut(&mut self) -> &mut Self::Target
{
&mut self.0
}
}
impl Into<Entity> for TargetId
{
fn into(self) -> Entity
{
self.0
}
}
pub trait UiReactEntityCommandsExt
{
fn insert_reactive<T: ReactComponent>(&mut self, component: T) -> &mut Self;
fn on_event<T: Send + Sync + 'static>(&mut self) -> OnEventExt<'_, T>;
fn despawn_on_event<T: Send + Sync + 'static>(&mut self) -> &mut Self;
fn despawn_on_broadcast<T: Send + Sync + 'static>(&mut self) -> &mut Self;
fn reactor<M, C, T, R>(&mut self, triggers: T, reactor: C) -> &mut Self
where
T: ReactionTriggerBundle,
R: CobwebResult,
C: IntoSystem<TargetId, R, M> + Send + Sync + 'static;
fn update<M, R: CobwebResult, C: IntoSystem<TargetId, R, M> + Send + Sync + 'static>(
&mut self,
callback: C,
) -> &mut Self;
fn update_on<M, C, T, R>(&mut self, triggers: T, reactor: C) -> &mut Self
where
T: ReactionTriggerBundle,
R: CobwebResult,
C: IntoSystem<TargetId, R, M> + Send + Sync + 'static;
fn modify(&mut self, callback: impl FnMut(EntityCommands) + Send + Sync + 'static) -> &mut Self;
}
impl UiReactEntityCommandsExt for EntityCommands<'_>
{
fn insert_reactive<T: ReactComponent>(&mut self, component: T) -> &mut Self
{
let id = self.id();
self.commands().react().insert(id, component);
self
}
fn on_event<T: Send + Sync + 'static>(&mut self) -> OnEventExt<'_, T>
{
let id = self.id();
OnEventExt::new(self.commands(), id)
}
fn despawn_on_event<T: Send + Sync + 'static>(&mut self) -> &mut Self
{
let entity = self.id();
self.on_event::<T>().r(move |mut c: Commands| {
let _ = c.get_entity(entity).map(|mut e| e.despawn());
});
self
}
fn despawn_on_broadcast<T: Send + Sync + 'static>(&mut self) -> &mut Self
{
let entity = self.id();
self.react().once(broadcast::<T>(), move |mut c: Commands| {
let _ = c.get_entity(entity).map(|mut e| e.despawn());
});
self
}
fn reactor<M, C, T, R>(&mut self, triggers: T, reactor: C) -> &mut Self
where
T: ReactionTriggerBundle,
R: CobwebResult,
C: IntoSystem<TargetId, R, M> + Send + Sync + 'static,
{
if TypeId::of::<T>() == TypeId::of::<()>() {
return self;
}
let id = self.id();
let mut reactor = RawCallbackSystem::new(reactor);
let callback = move |world: &mut World| {
let result = reactor.run_with_cleanup(world, id, |_| {});
result.handle(world);
};
let syscommand = self.commands().spawn_system_command(callback);
register_reactor(&mut self.commands(), id, syscommand, triggers);
self
}
fn update<M, R: CobwebResult, C: IntoSystem<TargetId, R, M> + Send + Sync + 'static>(
&mut self,
callback: C,
) -> &mut Self
{
self.update_on((), callback)
}
fn update_on<M, C, T, R>(&mut self, triggers: T, reactor: C) -> &mut Self
where
T: ReactionTriggerBundle,
R: CobwebResult,
C: IntoSystem<TargetId, R, M> + Send + Sync + 'static,
{
let id = self.id();
let mut reactor = RawCallbackSystem::new(reactor);
let callback = move |world: &mut World| {
let result = reactor.run_with_cleanup(world, id, |_| {});
result.handle(world);
};
let syscommand = self.commands().spawn_system_command(callback);
#[cfg(feature = "hot_reload")]
{
self.commands()
.syscall((id, syscommand, triggers), register_update_on_reactor);
}
#[cfg(not(feature = "hot_reload"))]
{
register_update_on_reactor(&mut self.commands(), id, syscommand, triggers);
}
self
}
fn modify(&mut self, mut callback: impl FnMut(EntityCommands) + Send + Sync + 'static) -> &mut Self
{
self.update_on((), move |id: TargetId, mut c: Commands| {
let Ok(ec) = c.get_entity(*id) else { return };
(callback)(ec)
})
}
}
pub(crate) struct ReactorExtPlugin;
impl Plugin for ReactorExtPlugin
{
fn build(&self, _app: &mut App) {}
}