use std::borrow::Cow;
use bevy_ecs::{
archetype::{Archetype, ArchetypeComponentId},
component::ComponentId,
event::Events,
query::Access,
system::{In, IntoChainSystem, IntoSystem, Res, Resource, System},
world::World,
};
#[cfg(feature = "states")]
use crate::state::CurrentState;
pub struct ConditionalSystem<S: System> {
system: S,
conditions: Vec<Box<dyn System<In = (), Out = bool>>>,
component_access: Access<ComponentId>,
archetype_component_access: Access<ArchetypeComponentId>,
}
impl<Out: Default, S: System<Out = Out>> System for ConditionalSystem<S> {
type In = S::In;
type Out = Out;
fn name(&self) -> Cow<'static, str> {
self.system.name()
}
fn new_archetype(&mut self, archetype: &Archetype) {
for condition_system in self.conditions.iter_mut() {
condition_system.new_archetype(archetype);
self.archetype_component_access
.extend(condition_system.archetype_component_access());
}
self.system.new_archetype(archetype);
self.archetype_component_access
.extend(self.system.archetype_component_access());
}
fn component_access(&self) -> &Access<ComponentId> {
&self.component_access
}
fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.archetype_component_access
}
fn is_send(&self) -> bool {
let conditions_are_send = self.conditions.iter().all(|system| system.is_send());
self.system.is_send() && conditions_are_send
}
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
for condition_system in self.conditions.iter_mut() {
if !condition_system.run_unsafe((), world) {
return Out::default();
}
}
self.system.run_unsafe(input, world)
}
fn apply_buffers(&mut self, world: &mut World) {
for condition_system in self.conditions.iter_mut() {
condition_system.apply_buffers(world);
}
self.system.apply_buffers(world);
}
fn initialize(&mut self, world: &mut World) {
for condition_system in self.conditions.iter_mut() {
condition_system.initialize(world);
self.component_access
.extend(condition_system.component_access());
}
self.system.initialize(world);
self.component_access.extend(self.system.component_access());
}
fn check_change_tick(&mut self, change_tick: u32) {
for condition_system in self.conditions.iter_mut() {
condition_system.check_change_tick(change_tick);
}
self.system.check_change_tick(change_tick);
}
}
impl<S: System> ConditionalSystem<S> {
pub fn run_if<Condition, Params>(mut self, condition: Condition) -> Self
where
Condition: IntoSystem<(), bool, Params>,
{
let condition_system = condition.system();
self.conditions.push(Box::new(condition_system));
self
}
pub fn run_if_not<Condition, Params>(self, condition: Condition) -> Self
where
Condition: IntoSystem<(), bool, Params>,
{
self.run_if(condition.chain(move |In(x): In<bool>| !x))
}
pub fn run_on_event<T: Send + Sync + 'static>(self) -> Self {
self.run_if(move |ev: Res<Events<T>>| !ev.is_empty())
}
pub fn run_if_resource_exists<T: Resource>(self) -> Self {
self.run_if(move |res: Option<Res<T>>| res.is_some())
}
pub fn run_unless_resource_exists<T: Resource>(self) -> Self {
self.run_if(move |res: Option<Res<T>>| res.is_none())
}
pub fn run_if_resource_equals<T: Resource + PartialEq>(self, value: T) -> Self {
self.run_if(move |res: Option<Res<T>>| {
if let Some(res) = res {
*res == value
} else {
false
}
})
}
pub fn run_unless_resource_equals<T: Resource + PartialEq>(self, value: T) -> Self {
self.run_if(move |res: Option<Res<T>>| {
if let Some(res) = res {
*res != value
} else {
false
}
})
}
#[cfg(feature = "states")]
pub fn run_in_state<T: bevy_ecs::schedule::StateData>(self, state: T) -> Self {
self.run_if_resource_equals(CurrentState(state))
}
#[cfg(feature = "states")]
pub fn run_not_in_state<T: bevy_ecs::schedule::StateData>(self, state: T) -> Self {
self.run_unless_resource_equals(CurrentState(state))
}
#[cfg(feature = "bevy-compat")]
pub fn run_in_bevy_state<T: bevy_ecs::schedule::StateData>(self, state: T) -> Self {
self.run_if(move |res: Option<Res<bevy_ecs::schedule::State<T>>>| {
if let Some(res) = res {
res.current() == &state
} else {
false
}
})
}
#[cfg(feature = "bevy-compat")]
pub fn run_not_in_bevy_state<T: bevy_ecs::schedule::StateData>(self, state: T) -> Self {
self.run_if(move |res: Option<Res<bevy_ecs::schedule::State<T>>>| {
if let Some(res) = res {
res.current() != &state
} else {
false
}
})
}
}
pub trait IntoConditionalSystem<In, Out, Params>: IntoSystem<In, Out, Params> + Sized {
fn into_conditional(self) -> ConditionalSystem<Self::System>;
fn run_if<Condition, CondParams>(self, condition: Condition) -> ConditionalSystem<Self::System>
where
Condition: IntoSystem<(), bool, CondParams>,
{
self.into_conditional().run_if(condition)
}
fn run_if_not<Condition, CondParams>(
self,
condition: Condition,
) -> ConditionalSystem<Self::System>
where
Condition: IntoSystem<(), bool, CondParams>,
{
self.into_conditional().run_if_not(condition)
}
fn run_on_event<T: Send + Sync + 'static>(self) -> ConditionalSystem<Self::System> {
self.into_conditional().run_on_event::<T>()
}
fn run_if_resource_exists<T: Resource>(self) -> ConditionalSystem<Self::System> {
self.into_conditional().run_if_resource_exists::<T>()
}
fn run_unless_resource_exists<T: Resource>(self) -> ConditionalSystem<Self::System> {
self.into_conditional().run_unless_resource_exists::<T>()
}
fn run_if_resource_equals<T: Resource + PartialEq>(
self,
value: T,
) -> ConditionalSystem<Self::System> {
self.into_conditional().run_if_resource_equals(value)
}
fn run_unless_resource_equals<T: Resource + PartialEq>(
self,
value: T,
) -> ConditionalSystem<Self::System> {
self.into_conditional().run_unless_resource_equals(value)
}
#[cfg(feature = "states")]
fn run_in_state<T: bevy_ecs::schedule::StateData>(
self,
state: T,
) -> ConditionalSystem<Self::System> {
self.into_conditional().run_in_state(state)
}
#[cfg(feature = "states")]
fn run_not_in_state<T: bevy_ecs::schedule::StateData>(
self,
state: T,
) -> ConditionalSystem<Self::System> {
self.into_conditional().run_not_in_state(state)
}
#[cfg(feature = "bevy-compat")]
fn run_in_bevy_state<T: bevy_ecs::schedule::StateData>(
self,
state: T,
) -> ConditionalSystem<Self::System> {
self.into_conditional().run_in_bevy_state(state)
}
#[cfg(feature = "bevy-compat")]
fn run_not_in_bevy_state<T: bevy_ecs::schedule::StateData>(
self,
state: T,
) -> ConditionalSystem<Self::System> {
self.into_conditional().run_not_in_bevy_state(state)
}
}
impl<S, In, Out, Params> IntoConditionalSystem<In, Out, Params> for S
where
S: IntoSystem<In, Out, Params>,
{
fn into_conditional(self) -> ConditionalSystem<Self::System> {
ConditionalSystem {
system: self.system(),
conditions: Vec::new(),
archetype_component_access: Default::default(),
component_access: Default::default(),
}
}
}