bevy_event_chain 0.1.0

Easy trait-like behavior using observers and relations in Bevy
Documentation
#![doc = include_str!("../README.md")]

use bevy::ecs::component::{ComponentId, Components};
use bevy::ecs::relationship::{Relationship, RelationshipSourceCollection};
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;

pub use crate::trigger::EntityComponentTrigger;
pub use bevy_event_chain_derive::{ChainEvent, RelatedChainEvent};

mod trigger;

/// A tracker for the chain of entities an event is propagating through.
#[derive(Clone, Debug, Reflect)]
pub struct EventChain {
    target: Entity,
    target_index: usize,
    targets: Vec<Entity>,
}

impl EventChain {
    pub fn new(targets: Vec<Entity>) -> Self {
        Self {
            target: targets[0],
            target_index: 0,
            targets,
        }
    }

    /// Helper to create a new [`EventChain`] with a specified relationship target.
    pub fn new_with_end(mut targets: Vec<Entity>, end: Entity) -> Self {
        targets.push(end);
        Self::new(targets)
    }

    /// Creates a new chain targetting the next target entity. Panics if there is no next target.
    pub fn next(&self) -> Self {
        Self {
            target: self.targets[self.target_index + 1],
            target_index: self.target_index + 1,
            targets: self.targets.clone(),
        }
    }

    /// Returns the current event target entity.
    pub fn event_target(&self) -> Entity {
        self.target
    }

    /// Returns a mutable reference to the current event target entity.
    pub fn event_target_mut(&mut self) -> &mut Entity {
        &mut self.target
    }

    /// Creates a new [`EventChain`] with the targets in reverse order. The current target remains the same.
    pub fn reversed(&self) -> Self {
        let target_index = self.targets.len() - self.target_index - 1;
        let mut targets = self.targets.clone();
        targets.reverse();
        Self {
            target: targets[target_index],
            target_index,
            targets,
        }
    }
}

/// An event that propagates through a chain of entities.
pub trait ChainEvent: EntityEvent + Clone {
    /// Creates a new event targeting the next entity in the chain.
    fn next(&self) -> Self;
}

/// A tracker for the chain of entities an event is propagating through, with relationship context.
#[derive(Clone, Debug, Reflect)]
pub struct RelatedEventChain {
    chain: EventChain,
    relationship_target: ComponentId,
    relationship: ComponentId,
}

impl RelatedEventChain {
    /// Creates a new chain targetting the next target entity. Panics if there is no next target.
    pub fn next(&self) -> Self {
        Self {
            chain: self.chain.next(),
            relationship_target: self.relationship_target,
            relationship: self.relationship,
        }
    }

    /// Creates a new trigger for the current event target with what *should* be its relationship component.
    pub fn get_trigger(&self) -> EntityComponentTrigger {
        if self.chain.target_index + 1 == self.chain.targets.len() {
            EntityComponentTrigger {
                component: self.relationship_target,
            }
        } else {
            EntityComponentTrigger {
                component: self.relationship,
            }
        }
    }

    /// Returns the current event target entity.
    pub fn event_target(&self) -> Entity {
        self.chain.event_target()
    }

    /// Returns a mutable reference to the current event target entity.
    pub fn event_target_mut(&mut self) -> &mut Entity {
        self.chain.event_target_mut()
    }

    /// Creates a new [`RelatedEventChain`] with the targets in reverse order. The current target and the final relationship target remain the same.
    pub fn reversed(&self) -> Self {
        let mut chain = self.chain.reversed();
        let rel_target = chain.targets.remove(0);
        chain.targets.push(rel_target);
        Self {
            chain,
            relationship_target: self.relationship_target,
            relationship: self.relationship,
        }
    }
}

/// An event that propagates through a chain of entities with relationship context.
pub trait RelatedChainEvent:
    for<'a> Event<Trigger<'a> = EntityComponentTrigger> + EntityEvent + Clone
{
    type Relationship: Relationship<RelationshipTarget = Self::RelationshipTarget>;
    type RelationshipTarget: RelationshipTarget<Relationship = Self::Relationship>;

    /// Creates a new event targeting the next entity in the chain.
    fn next(&self) -> Self;

    /// Creates a new trigger for the current event target with what *should* be its relationship component.
    fn get_trigger(&self) -> EntityComponentTrigger;

    /// Triggers the event for the current target entity, automatically creating its trigger.
    fn trigger(self, commands: &mut Commands) {
        let this_trigger = self.get_trigger();
        commands.trigger_with(self, this_trigger);
    }

    /// Triggers the event for the next target entity in the chain, automatically creating its trigger.
    fn trigger_next(&self, commands: &mut Commands) {
        self.next().trigger(commands);
    }
}

/// A helper system parameter to create new [`RelatedEventChain`]s for new [`RelatedChainEvent`]s.
#[derive(SystemParam)]
pub struct ChainEventRelation<'w, 's, Event: RelatedChainEvent> {
    relation: Query<'w, 's, &'static <Event as RelatedChainEvent>::RelationshipTarget>,
    components: &'w Components,
}

impl<'w, 's, Event: RelatedChainEvent> ChainEventRelation<'w, 's, Event> {
    /// Creates a new [`RelatedEventChain`] with the specified relationship target.
    pub fn new_chain(&self, relationship_target: Entity) -> RelatedEventChain {
        let targets = self
            .relation
            .get(relationship_target)
            .unwrap() // might fail, should probably turn this into a Result
            .collection()
            .iter()
            .chain([relationship_target])
            .collect::<Vec<_>>();

        RelatedEventChain {
            chain: EventChain::new(targets),
            relationship_target: self
                .components
                .component_id::<Event::RelationshipTarget>()
                .unwrap(), // probably won't fail
            relationship: self
                .components
                .component_id::<Event::Relationship>()
                .unwrap(),
        }
    }
}