Created
April 13, 2021 17:08
-
-
Save TehPers/919421de75e16b31dc703706a7e60b09 to your computer and use it in GitHub Desktop.
Bevy 0.5 run criteria combinators
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//! Defines several run criteria combinators for bevy 0.5. There are four | |
//! combinators defined in this file: | |
//! - Piping combinators ([`and`], [`or`]): Allows two run criteria to be | |
//! combined via the use of labels and pipes. To use these, define your | |
//! preceeding run criteria on your stage via | |
//! [`Stage::add_system_run_criteria()`] or | |
//! [`Stage::with_system_run_criteria()`] and label it, then use the | |
//! combinator by wrapping your second run criteria with it and passing it | |
//! to [`RunCriteria::pipe()`] along with the label for your preceeding | |
//! criteria. | |
//! - Compound combinators ([`if_all`], [`if_any`]): Allows many run criteria | |
//! to be combined, however does not support run criteria labels. | |
//! | |
//! All combinators are short-circuiting, meaning they are not executed unless | |
//! they are needed to in order to determine if a system should run. | |
use std::borrow::Cow; | |
use bevy::{ | |
ecs::{ | |
archetype::{Archetype, ArchetypeComponentId}, | |
component::ComponentId, | |
query::Access, | |
schedule::ShouldRun, | |
system::SystemId, | |
}, | |
prelude::*, | |
}; | |
pub fn identity<T>(input: In<T>) -> T { | |
input.0 | |
} | |
macro_rules! impl_simple_run_criteria { | |
($(#[$struct_attr:meta])* $struct_name:ident, $(#[$func_attr:meta])* $func_name:ident, ($self:ident, $input:ident, $world:ident) $run:block) => { | |
$(#[$func_attr])* | |
pub fn $func_name(criterion: impl System<In = (), Out = ShouldRun>) -> impl System<In = ShouldRun, Out = ShouldRun> { | |
$struct_name { | |
connector: identity::<ShouldRun>.system(), | |
criterion, | |
system_id: SystemId::new(), | |
archetype_component_access: Default::default(), | |
component_access: Default::default(), | |
} | |
} | |
$(#[$struct_attr])* | |
struct $struct_name<C, S> | |
where | |
C: System<In = ShouldRun, Out = ShouldRun>, | |
S: System<In = (), Out = ShouldRun>, | |
{ | |
connector: C, | |
criterion: S, | |
system_id: SystemId, | |
archetype_component_access: Access<ArchetypeComponentId>, | |
component_access: Access<ComponentId>, | |
} | |
impl<C, S> System for $struct_name<C, S> | |
where | |
C: System<In = ShouldRun, Out = ShouldRun>, | |
S: System<In = (), Out = ShouldRun>, | |
{ | |
type In = ShouldRun; | |
type Out = ShouldRun; | |
fn name(&self) -> Cow<'static, str> { | |
Cow::Borrowed(std::any::type_name::<Self>()) | |
} | |
fn id(&self) -> SystemId { | |
self.system_id | |
} | |
fn new_archetype(&mut self, archetype: &Archetype) { | |
self.connector.new_archetype(archetype); | |
self.criterion.new_archetype(archetype); | |
self.archetype_component_access | |
.extend(self.connector.archetype_component_access()); | |
self.archetype_component_access | |
.extend(self.criterion.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 { | |
self.connector.is_send() && self.criterion.is_send() | |
} | |
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { | |
let $self = self; | |
let $input = input; | |
let $world = world; | |
$run | |
} | |
fn apply_buffers(&mut self, world: &mut World) { | |
self.connector.apply_buffers(world); | |
self.criterion.apply_buffers(world); | |
} | |
fn initialize(&mut self, world: &mut World) { | |
self.connector.initialize(world); | |
self.criterion.initialize(world); | |
self.component_access.extend(self.connector.component_access()); | |
self.component_access.extend(self.criterion.component_access()); | |
} | |
fn check_change_tick(&mut self, change_tick: u32) { | |
self.connector.check_change_tick(change_tick); | |
self.criterion.check_change_tick(change_tick); | |
} | |
} | |
}; | |
} | |
macro_rules! impl_compound_run_criteria { | |
($(#[$struct_attr:meta])* $struct_name:ident, $(#[$func_attr:meta])* $func_name:ident, ($self:ident, $input:ident, $world:ident) $run:block) => { | |
$(#[$func_attr])* | |
pub fn $func_name<In: Clone + 'static>(criteria: Vec<Box<dyn System<In = In, Out = ShouldRun>>>) -> impl System<In = In, Out = ShouldRun> { | |
$struct_name::new(criteria) | |
} | |
$(#[$struct_attr])* | |
struct $struct_name<In> | |
where | |
In: Clone + 'static, | |
{ | |
criteria: Vec<Box<dyn System<In = In, Out = ShouldRun>>>, | |
system_id: SystemId, | |
archetype_component_access: Access<ArchetypeComponentId>, | |
component_access: Access<ComponentId>, | |
} | |
impl<In> $struct_name<In> | |
where | |
In: Clone + 'static, | |
{ | |
pub fn new(criteria: Vec<Box<dyn System<In = In, Out = ShouldRun>>>) -> Self { | |
$struct_name { | |
criteria, | |
system_id: SystemId::new(), | |
archetype_component_access: Default::default(), | |
component_access: Default::default(), | |
} | |
} | |
} | |
impl<In> System for $struct_name<In> | |
where | |
In: Clone + 'static, | |
{ | |
type In = In; | |
type Out = ShouldRun; | |
fn name(&self) -> Cow<'static, str> { | |
Cow::Borrowed(std::any::type_name::<Self>()) | |
} | |
fn id(&self) -> SystemId { | |
self.system_id | |
} | |
fn new_archetype(&mut self, archetype: &Archetype) { | |
for criterion in self.criteria.iter_mut() { | |
criterion.new_archetype(archetype); | |
self.archetype_component_access | |
.extend(criterion.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 { | |
self.criteria.iter().all(|criterion| criterion.is_send()) | |
} | |
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out { | |
let $self = self; | |
let $input = input; | |
let $world = world; | |
$run | |
} | |
fn apply_buffers(&mut self, world: &mut World) { | |
for criterion in self.criteria.iter_mut() { | |
criterion.apply_buffers(world); | |
} | |
} | |
fn initialize(&mut self, world: &mut World) { | |
for criterion in self.criteria.iter_mut() { | |
criterion.initialize(world); | |
self.component_access.extend(criterion.component_access()); | |
} | |
} | |
fn check_change_tick(&mut self, change_tick: u32) { | |
for criterion in self.criteria.iter_mut() { | |
criterion.check_change_tick(change_tick); | |
} | |
} | |
} | |
}; | |
} | |
fn split_should_run(should_run: ShouldRun) -> (bool, bool) { | |
match should_run { | |
ShouldRun::NoAndCheckAgain => (false, true), | |
ShouldRun::No => (false, false), | |
ShouldRun::YesAndCheckAgain => (true, true), | |
ShouldRun::Yes => (true, false), | |
} | |
} | |
fn merge_should_run(run: bool, check_again: bool) -> ShouldRun { | |
match (run, check_again) { | |
(false, false) => ShouldRun::No, | |
(false, true) => ShouldRun::NoAndCheckAgain, | |
(true, false) => ShouldRun::Yes, | |
(true, true) => ShouldRun::YesAndCheckAgain, | |
} | |
} | |
impl_simple_run_criteria!( | |
AndIf, | |
/// Intended to be used with [`RunCriteria::pipe()`]. Allows a run | |
/// criterion that takes no [`ShouldRun`] as input to be piped into. | |
/// | |
/// Runs a system if: | |
/// - Both the preceeding and inner criteria succeed. | |
/// - If the preceedding criteria fails, the inner criterion will not be | |
/// executed. | |
/// | |
/// Checks again if: | |
/// - The preceeding criteria returns [`ShouldRun::No`], then no. | |
/// - Otherwise, either the preceeding or inner criteria request to be | |
/// checked again. | |
/// | |
/// This is short-circuiting. If the preceeding run criteria returns | |
/// [`ShouldRun::No`] or [`ShouldRun::NoAndCheckAgain`], then that result | |
/// is returned and the inner criterion is not executed. | |
and, | |
(this, input, world) { | |
let (connector_run, connector_check_again) = split_should_run(this.connector.run_unsafe(input, world)); | |
if connector_run { | |
let (criterion_run, criterion_check_again) = split_should_run(this.criterion.run_unsafe((), world)); | |
merge_should_run(criterion_run, connector_check_again || criterion_check_again) | |
} else { | |
merge_should_run(false, connector_check_again) | |
} | |
} | |
); | |
impl_simple_run_criteria!( | |
OrIf, | |
/// Intended to be used with [`RunCriteria::pipe()`]. Allows a run | |
/// criterion that takes no [`ShouldRun`] as input to be piped into. | |
/// | |
/// Runs a system if: | |
/// - Either the preceeding or inner criteria succeed. | |
/// - If the preceedding criteria succeeds, the inner criterion will not | |
/// be executed. | |
/// | |
/// Checks again if: | |
/// - The preceeding criteria returns [`ShouldRun::Yes`], then no. | |
/// - Otherwise, either the preceeding or inner criteria request to be | |
/// checked again. | |
/// | |
/// This is short-circuiting. If the preceeding run criteria returns | |
/// [`ShouldRun::Yes`] or [`ShouldRun::YesAndCheckAgain`], then that result | |
/// is returned and the inner criterion is not executed. | |
or, | |
(this, input, world) { | |
let (connector_run, connector_check_again) = split_should_run(this.connector.run_unsafe(input, world)); | |
if connector_run { | |
merge_should_run(true, connector_check_again) | |
} else { | |
let (criterion_run, criterion_check_again) = split_should_run(this.criterion.run_unsafe((), world)); | |
merge_should_run(criterion_run, connector_check_again || criterion_check_again) | |
} | |
} | |
); | |
impl_compound_run_criteria!( | |
RunIfAll, | |
/// Runs a system if: | |
/// - All the inner criteria succeed (or if there are no inner criteria). | |
/// - If any criterion fails, then any subsequent criteria will not be | |
/// executed. | |
/// | |
/// Checks again if: | |
/// - Any of the executed inner criteria request to be checked again. | |
/// | |
/// This is short-circuiting. If any inner criteria return | |
/// [`ShouldRun::No`] or [`ShouldRun::NoAndCheckAgain`], then that result | |
/// is returned and the rest are not executed. | |
/// | |
/// Criteria execution order follows the order in the inner vector. | |
if_all, | |
(this, input, world) { | |
let mut check_again = false; | |
let run = this.criteria.iter_mut().all(|criterion| { | |
let (run, cur_check_again) = split_should_run(criterion.run_unsafe(input.clone(), world)); | |
check_again |= cur_check_again; | |
run | |
}); | |
merge_should_run(run, check_again) | |
} | |
); | |
impl_compound_run_criteria!( | |
RunIfAny, | |
/// Runs a system if: | |
/// - Any the inner criteria succeed (and if there are inner criteria). | |
/// - If any criterion succeeds, then any subsequent criteria will not be | |
/// executed. | |
/// | |
/// Checks again if: | |
/// - Any of the executed inner criteria request to be checked again. | |
/// | |
/// This is short-circuiting. If any inner criteria return | |
/// [`ShouldRun::Yes`] or [`ShouldRun::YesAndCheckAgain`], then that result | |
/// is returned and the rest are not executed. | |
/// | |
/// Criteria execution order follows the order in the inner vector. | |
if_any, | |
(this, input, world) { | |
let mut check_again = false; | |
let run = this.criteria.iter_mut().any(|criterion| { | |
let (run, cur_check_again) = split_should_run(criterion.run_unsafe(input.clone(), world)); | |
check_again |= cur_check_again; | |
run | |
}); | |
merge_should_run(run, check_again) | |
} | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment