use crate::prelude::*;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use std::any::{type_name, TypeId};
use std::marker::PhantomData;
struct ReactComponentId<T: ReactComponent>
{
id: TypeId,
p: PhantomData<T>,
}
impl<T: ReactComponent> ReactComponentId<T>
{
fn id(&self) -> TypeId
{
self.id
}
}
impl<T: ReactComponent> FromWorld for ReactComponentId<T>
{
fn from_world(_world: &mut World) -> Self
{
Self{
id: TypeId::of::<T>(),
p: PhantomData::default(),
}
}
}
#[derive(Resource)]
pub(crate) struct EntityReactionAccessTracker
{
currently_reacting: bool,
system: SystemCommand,
reaction_source: Entity,
reaction_type: EntityReactionType,
prepared: Vec<(SystemCommand, Entity, EntityReactionType)>,
}
impl EntityReactionAccessTracker
{
pub(crate) fn prepare(&mut self, system: SystemCommand, source: Entity, reaction: EntityReactionType)
{
self.prepared.push((system, source, reaction));
}
pub(crate) fn start(&mut self, reactor: SystemCommand)
{
let Some(pos) = self.prepared.iter().position(|(s, _, _)| *s == reactor) else {
tracing::error!("prepared entity reaction is missing {:?}", reactor);
debug_assert!(false);
return;
};
let (system, source, reaction) = self.prepared.swap_remove(pos);
debug_assert!(!self.currently_reacting);
self.currently_reacting = true;
self.system = system;
self.reaction_source = source;
self.reaction_type = reaction;
}
pub(crate) fn end(&mut self)
{
self.currently_reacting = false;
}
fn is_reacting(&self) -> bool
{
self.currently_reacting
}
fn system(&self) -> SystemCommand
{
self.system
}
fn source(&self) -> Entity
{
self.reaction_source
}
fn reaction_type(&self) -> EntityReactionType
{
self.reaction_type
}
}
impl Default for EntityReactionAccessTracker
{
fn default() -> Self
{
Self{
currently_reacting: false,
system: SystemCommand(Entity::PLACEHOLDER),
reaction_source: Entity::PLACEHOLDER,
reaction_type: EntityReactionType::Insertion(TypeId::of::<()>()),
prepared: Vec::default(),
}
}
}
#[derive(SystemParam)]
pub struct InsertionEvent<'w, 's, T: ReactComponent>
{
component_id: Local<'s, ReactComponentId<T>>,
tracker: Res<'w, EntityReactionAccessTracker>,
}
impl<'w, 's, T: ReactComponent> InsertionEvent<'w, 's, T>
{
pub fn entity(&self) -> Entity
{
self.get()
.unwrap_or_else(|| panic!("failed reading insertion event for {}, there is no event", type_name::<T>()))
}
pub fn get(&self) -> Option<Entity>
{
if !self.tracker.is_reacting() { return None; }
let EntityReactionType::Insertion(component_id) = self.tracker.reaction_type() else { return None; };
if component_id != self.component_id.id() { return None; }
Some(self.tracker.source())
}
pub fn is_empty(&self) -> bool
{
self.get().is_none()
}
}
#[derive(SystemParam)]
pub struct MutationEvent<'w, 's, T: ReactComponent>
{
component_id: Local<'s, ReactComponentId<T>>,
tracker: Res<'w, EntityReactionAccessTracker>,
}
impl<'w, 's, T: ReactComponent> MutationEvent<'w, 's, T>
{
pub fn entity(&self) -> Entity
{
self.get()
.unwrap_or_else(|| panic!("failed reading mutation event for {}, there is no event", type_name::<T>()))
}
pub fn get(&self) -> Option<Entity>
{
if !self.tracker.is_reacting() { return None; }
let EntityReactionType::Mutation(component_id) = self.tracker.reaction_type() else { return None; };
if component_id != self.component_id.id() { return None; }
Some(self.tracker.source())
}
pub fn is_empty(&self) -> bool
{
self.get().is_none()
}
}
#[derive(SystemParam)]
pub struct RemovalEvent<'w, 's, T: ReactComponent>
{
component_id: Local<'s, ReactComponentId<T>>,
tracker: Res<'w, EntityReactionAccessTracker>,
}
impl<'w, 's, T: ReactComponent> RemovalEvent<'w, 's, T>
{
pub fn entity(&self) -> Entity
{
self.get()
.unwrap_or_else(|| panic!("failed reading removal event for {}, there is no event", type_name::<T>()))
}
pub fn get(&self) -> Option<Entity>
{
if !self.tracker.is_reacting() { return None; }
let EntityReactionType::Removal(component_id) = self.tracker.reaction_type() else { return None; };
if component_id != self.component_id.id() { return None; }
Some(self.tracker.source())
}
pub fn is_empty(&self) -> bool
{
self.get().is_none()
}
}
#[derive(SystemParam)]
pub struct EntityLocal<'w, 's, T: EntityWorldReactor>
{
reactor: EntityReactor<'w, T>,
tracker: Res<'w, EntityReactionAccessTracker>,
data: Query<'w, 's, &'static mut EntityWorldLocal<T>>,
}
impl<'w, 's, T: EntityWorldReactor> EntityLocal<'w, 's, T>
{
pub fn entity(&self) -> Entity
{
self.check();
self.tracker.source()
}
pub fn get(&self) -> (Entity, &T::Local)
{
self.check();
(
self.tracker.source(),
self.data.get(self.tracker.source()).expect("entity missing local data in EntityLocal").inner()
)
}
pub fn get_mut(&mut self) -> (Entity, &mut T::Local)
{
self.check();
(
self.tracker.source(),
self.data.get_mut(self.tracker.source())
.expect("entity missing local data in EntityLocal")
.into_inner()
.inner_mut()
)
}
fn check(&self)
{
if !self.tracker.is_reacting()
{
panic!("EntityLocal should only be used in an EntityWorldReactor");
}
if self.tracker.system() != self.reactor.system().expect("EntityLocal should only be used in an EntityWorldReactor")
{
panic!("EntityLocal should only be used in an EntityWorldReactor");
}
}
}