use alloc::boxed::Box;
#[cfg(feature = "bevy_reflect")]
use bevy_ecs::reflect::ReflectComponent;
use bevy_ecs::{
component::Component,
entity::Entity,
entity_disabling::Disabled,
hierarchy::Children,
message::MessageReader,
query::{Allow, With},
system::{Commands, Query},
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
use crate::state::{StateTransitionEvent, States};
#[derive(Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct DespawnWhen<S: States> {
pub state_transition_evaluator:
Box<dyn Fn(&StateTransitionEvent<S>) -> bool + Sync + Send + 'static>,
}
impl<S: States> DespawnWhen<S> {
pub fn new(f: impl Fn(&StateTransitionEvent<S>) -> bool + Sync + Send + 'static) -> Self {
Self {
state_transition_evaluator: Box::new(f),
}
}
}
pub fn despawn_entities_when_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &DespawnWhen<S>), Allow<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
for (entity, when) in &query {
if (when.state_transition_evaluator)(transition) {
commands.entity(entity).try_despawn();
}
}
}
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
pub struct DespawnOnExit<S: States>(pub S);
impl<S> Default for DespawnOnExit<S>
where
S: States + Default,
{
fn default() -> Self {
Self(S::default())
}
}
pub fn despawn_entities_on_exit_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &DespawnOnExit<S>), Allow<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(exited) = &transition.exited else {
return;
};
for (entity, exit) in &query {
if exit.0 == *exited {
commands.entity(entity).try_despawn();
}
}
}
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct DespawnOnEnter<S: States>(pub S);
impl<S: States + Default> Default for DespawnOnEnter<S> {
fn default() -> Self {
Self(S::default())
}
}
pub fn despawn_entities_on_enter_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &DespawnOnEnter<S>), Allow<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(entered) = &transition.entered else {
return;
};
for (entity, enter) in &query {
if enter.0 == *entered {
commands.entity(entity).try_despawn();
}
}
}
#[derive(Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct DisableWhen<S: States> {
pub state_transition_evaluator:
Box<dyn Fn(&StateTransitionEvent<S>) -> bool + Sync + Send + 'static>,
}
impl<S: States> DisableWhen<S> {
pub fn new(f: impl Fn(&StateTransitionEvent<S>) -> bool + Sync + Send + 'static) -> Self {
Self {
state_transition_evaluator: Box::new(f),
}
}
}
pub fn disable_entities_when_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &DisableWhen<S>), Allow<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
for (entity, when) in &query {
if (when.state_transition_evaluator)(transition) {
commands
.entity(entity)
.insert_recursive::<Children>(Disabled);
}
}
}
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
pub struct DisableOnExit<S: States>(pub S);
impl<S> Default for DisableOnExit<S>
where
S: States + Default,
{
fn default() -> Self {
Self(S::default())
}
}
pub fn disable_entities_on_exit_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &DisableOnExit<S>), Allow<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(exited) = &transition.exited else {
return;
};
for (entity, exit) in &query {
if exit.0 == *exited {
commands
.entity(entity)
.insert_recursive::<Children>(Disabled);
}
}
}
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct DisableOnEnter<S: States>(pub S);
impl<S: States + Default> Default for DisableOnEnter<S> {
fn default() -> Self {
Self(S::default())
}
}
pub fn disable_entities_on_enter_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &DisableOnEnter<S>), Allow<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(entered) = &transition.entered else {
return;
};
for (entity, enter) in &query {
if enter.0 == *entered {
commands
.entity(entity)
.insert_recursive::<Children>(Disabled);
}
}
}
#[derive(Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct EnableWhen<S: States> {
pub state_transition_evaluator:
Box<dyn Fn(&StateTransitionEvent<S>) -> bool + Sync + Send + 'static>,
}
impl<S: States> EnableWhen<S> {
pub fn new(f: impl Fn(&StateTransitionEvent<S>) -> bool + Sync + Send + 'static) -> Self {
Self {
state_transition_evaluator: Box::new(f),
}
}
}
pub fn enable_entities_when_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &EnableWhen<S>), With<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
for (entity, when) in &query {
if (when.state_transition_evaluator)(transition) {
commands
.entity(entity)
.remove_recursive::<Children, Disabled>();
}
}
}
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Clone))]
pub struct EnableOnExit<S: States>(pub S);
impl<S> Default for EnableOnExit<S>
where
S: States + Default,
{
fn default() -> Self {
Self(S::default())
}
}
pub fn enable_entities_on_exit_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &EnableOnExit<S>), With<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(exited) = &transition.exited else {
return;
};
for (entity, exit) in &query {
if exit.0 == *exited {
commands
.entity(entity)
.remove_recursive::<Children, Disabled>();
}
}
}
#[derive(Component, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct EnableOnEnter<S: States>(pub S);
impl<S: States + Default> Default for EnableOnEnter<S> {
fn default() -> Self {
Self(S::default())
}
}
pub fn enable_entities_on_enter_state<S: States>(
mut commands: Commands,
mut transitions: MessageReader<StateTransitionEvent<S>>,
query: Query<(Entity, &EnableOnEnter<S>), With<Disabled>>,
) {
let Some(transition) = transitions.read().last() else {
return;
};
if transition.entered == transition.exited && !transition.allow_same_state_transitions {
return;
}
let Some(entered) = &transition.entered else {
return;
};
for (entity, enter) in &query {
if enter.0 == *entered {
commands
.entity(entity)
.remove_recursive::<Children, Disabled>();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use bevy_app::App;
use crate::{
app::{AppExtStates, StatesPlugin},
prelude::CommandsStatesExt,
};
#[test]
fn despawn_on_exit_from_computed_state() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States)]
enum State {
On,
Off,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct ComputedState;
impl bevy_state::state::ComputedStates for ComputedState {
type SourceStates = State;
fn compute(sources: Self::SourceStates) -> Option<Self> {
match sources {
State::On => Some(ComputedState),
State::Off => None,
}
}
}
let mut app = App::new();
app.add_plugins(StatesPlugin);
app.insert_state(State::On);
app.add_computed_state::<ComputedState>();
app.update();
assert_eq!(
app.world()
.resource::<bevy_state::state::State<State>>()
.get(),
&State::On
);
assert_eq!(
app.world()
.resource::<bevy_state::state::State<ComputedState>>()
.get(),
&ComputedState
);
let entity = app.world_mut().spawn(DespawnOnExit(ComputedState)).id();
assert!(app.world().get_entity(entity).is_ok());
app.world_mut().commands().set_state(State::Off);
app.update();
assert_eq!(
app.world()
.resource::<bevy_state::state::State<State>>()
.get(),
&State::Off
);
assert!(app
.world()
.get_resource::<bevy_state::state::State<ComputedState>>()
.is_none());
assert!(app.world().get_entity(entity).is_err());
}
#[test]
fn despawn_on_exit_same_state_transition() {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, States)]
enum State {
On,
}
let mut app = App::new();
app.add_plugins(StatesPlugin);
app.insert_state(State::On);
app.update();
assert_eq!(
app.world()
.resource::<bevy_state::state::State<State>>()
.get(),
&State::On
);
let entity = app.world_mut().spawn(DespawnOnExit(State::On)).id();
assert!(app.world().get_entity(entity).is_ok());
app.world_mut().commands().set_state(State::On);
app.update();
assert_eq!(
app.world()
.resource::<bevy_state::state::State<State>>()
.get(),
&State::On
);
assert!(app.world().get_entity(entity).is_err());
let entity = app.world_mut().spawn(DespawnOnExit(State::On)).id();
assert!(app.world().get_entity(entity).is_ok());
app.world_mut().commands().set_state_if_neq(State::On);
app.update();
assert_eq!(
app.world()
.resource::<bevy_state::state::State<State>>()
.get(),
&State::On
);
assert!(app.world().get_entity(entity).is_ok());
}
}