Skip to content

Instantly share code, notes, and snippets.

@TehPers
Created April 13, 2021 17:08
Show Gist options
  • Save TehPers/919421de75e16b31dc703706a7e60b09 to your computer and use it in GitHub Desktop.
Save TehPers/919421de75e16b31dc703706a7e60b09 to your computer and use it in GitHub Desktop.
Bevy 0.5 run criteria combinators
//! 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