Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - System sets and run criteria v2 #1675

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions crates/bevy_app/src/app_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use bevy_ecs::{
world::{FromWorld, World},
};
use bevy_utils::tracing::debug;
use std::{fmt::Debug, hash::Hash};

/// Configure [App]s using the builder pattern
pub struct AppBuilder {
Expand Down Expand Up @@ -177,16 +178,18 @@ impl AppBuilder {
self
}

pub fn add_state<T: Component + Clone + Eq>(&mut self, initial: T) -> &mut Self {
pub fn add_state<T>(&mut self, initial: T) -> &mut Self
where
T: Component + Debug + Clone + Eq + Hash,
{
self.insert_resource(State::new(initial))
.add_system_set(State::<T>::make_driver())
}

pub fn add_state_to_stage<T: Component + Clone + Eq>(
&mut self,
stage: impl StageLabel,
initial: T,
) -> &mut Self {
pub fn add_state_to_stage<T>(&mut self, stage: impl StageLabel, initial: T) -> &mut Self
where
T: Component + Debug + Clone + Eq + Hash,
{
self.insert_resource(State::new(initial))
.add_system_set_to_stage(stage, State::<T>::make_driver())
}
Expand Down
6 changes: 6 additions & 0 deletions crates/bevy_ecs/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,12 @@ pub fn derive_ambiguity_set_label(input: TokenStream) -> TokenStream {
derive_label(input, Ident::new("AmbiguitySetLabel", Span::call_site())).into()
}

#[proc_macro_derive(RunCriteriaLabel)]
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
derive_label(input, Ident::new("RunCriteriaLabel", Span::call_site())).into()
}

fn derive_label(input: DeriveInput, label_type: Ident) -> TokenStream2 {
let ident = input.ident;
let ecs_path: Path = bevy_ecs_path();
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ pub mod prelude {
query::{Added, ChangeTrackers, Changed, Or, QueryState, With, WithBundle, Without},
schedule::{
AmbiguitySetLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion,
RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel, RunCriteriaPiping,
Schedule, Stage, StageLabel, State, SystemLabel, SystemSet, SystemStage,
},
system::{
Expand Down
125 changes: 125 additions & 0 deletions crates/bevy_ecs/src/schedule/graph_utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use bevy_utils::{tracing::warn, HashMap, HashSet};
use fixedbitset::FixedBitSet;
use std::{borrow::Cow, fmt::Debug, hash::Hash};

pub enum DependencyGraphError<Labels> {
GraphCycles(Vec<(usize, Labels)>),
}

pub trait GraphNode<Label> {
fn name(&self) -> Cow<'static, str>;
fn labels(&self) -> &[Label];
fn before(&self) -> &[Label];
fn after(&self) -> &[Label];
}

/// Constructs a dependency graph of given nodes.
pub fn build_dependency_graph<Node, Label>(
nodes: &[Node],
) -> HashMap<usize, HashMap<usize, HashSet<Label>>>
where
Node: GraphNode<Label>,
Label: Debug + Clone + Eq + Hash,
{
let mut labels = HashMap::<Label, FixedBitSet>::default();
for (label, index) in nodes.iter().enumerate().flat_map(|(index, container)| {
container
.labels()
.iter()
.cloned()
.map(move |label| (label, index))
}) {
labels
.entry(label)
.or_insert_with(|| FixedBitSet::with_capacity(nodes.len()))
.insert(index);
}
let mut graph = HashMap::with_capacity_and_hasher(nodes.len(), Default::default());
for (index, node) in nodes.iter().enumerate() {
let dependencies = graph.entry(index).or_insert_with(HashMap::default);
for label in node.after() {
match labels.get(label) {
Some(new_dependencies) => {
for dependency in new_dependencies.ones() {
dependencies
.entry(dependency)
.or_insert_with(HashSet::default)
.insert(label.clone());
}
}
None => warn!(
// TODO: plumb this as proper output?
"{} wants to be after unknown label: {:?}",
nodes[index].name(),
label
),
}
}
for label in node.before() {
match labels.get(label) {
Some(dependants) => {
for dependant in dependants.ones() {
graph
.entry(dependant)
.or_insert_with(HashMap::default)
.entry(index)
.or_insert_with(HashSet::default)
.insert(label.clone());
}
}
None => warn!(
"{} wants to be before unknown label: {:?}",
nodes[index].name(),
label
),
}
}
}
graph
}

/// Generates a topological order for the given graph.
pub fn topological_order<Labels: Clone>(
graph: &HashMap<usize, HashMap<usize, Labels>>,
) -> Result<Vec<usize>, DependencyGraphError<Labels>> {
fn check_if_cycles_and_visit<L>(
node: &usize,
graph: &HashMap<usize, HashMap<usize, L>>,
sorted: &mut Vec<usize>,
unvisited: &mut HashSet<usize>,
current: &mut Vec<usize>,
) -> bool {
if current.contains(node) {
return true;
} else if !unvisited.remove(node) {
return false;
}
current.push(*node);
for dependency in graph.get(node).unwrap().keys() {
if check_if_cycles_and_visit(dependency, &graph, sorted, unvisited, current) {
return true;
}
}
sorted.push(*node);
current.pop();
false
}
let mut sorted = Vec::with_capacity(graph.len());
let mut current = Vec::with_capacity(graph.len());
let mut unvisited = HashSet::with_capacity_and_hasher(graph.len(), Default::default());
unvisited.extend(graph.keys().cloned());
while let Some(node) = unvisited.iter().next().cloned() {
if check_if_cycles_and_visit(&node, graph, &mut sorted, &mut unvisited, &mut current) {
let mut cycle = Vec::new();
let last_window = [*current.last().unwrap(), current[0]];
let mut windows = current
.windows(2)
.chain(std::iter::once(&last_window as &[usize]));
while let Some(&[dependant, dependency]) = windows.next() {
cycle.push((dependant, graph[&dependant][&dependency].clone()));
}
return Err(DependencyGraphError::GraphCycles(cycle));
}
}
Ok(sorted)
}
9 changes: 8 additions & 1 deletion crates/bevy_ecs/src/schedule/label.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pub use bevy_ecs_macros::{AmbiguitySetLabel, StageLabel, SystemLabel};
pub use bevy_ecs_macros::{AmbiguitySetLabel, RunCriteriaLabel, StageLabel, SystemLabel};

use std::{
any::Any,
Expand Down Expand Up @@ -67,6 +67,12 @@ pub trait AmbiguitySetLabel: DynHash + Debug + Send + Sync + 'static {
}
pub(crate) type BoxedAmbiguitySetLabel = Box<dyn AmbiguitySetLabel>;

pub trait RunCriteriaLabel: DynHash + Debug + Send + Sync + 'static {
#[doc(hidden)]
fn dyn_clone(&self) -> Box<dyn RunCriteriaLabel>;
}
pub(crate) type BoxedRunCriteriaLabel = Box<dyn RunCriteriaLabel>;

macro_rules! impl_label {
($trait_name:ident) => {
impl PartialEq for dyn $trait_name {
Expand Down Expand Up @@ -106,3 +112,4 @@ macro_rules! impl_label {
impl_label!(StageLabel);
impl_label!(SystemLabel);
impl_label!(AmbiguitySetLabel);
impl_label!(RunCriteriaLabel);
119 changes: 6 additions & 113 deletions crates/bevy_ecs/src/schedule/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
mod executor;
mod executor_parallel;
pub mod graph_utils;
mod label;
mod run_criteria;
mod stage;
mod state;
mod system_container;
Expand All @@ -9,28 +11,26 @@ mod system_set;

pub use executor::*;
pub use executor_parallel::*;
pub use graph_utils::GraphNode;
pub use label::*;
pub use run_criteria::*;
pub use stage::*;
pub use state::*;
pub use system_container::*;
pub use system_descriptor::*;
pub use system_set::*;

use crate::{
archetype::{Archetype, ArchetypeComponentId},
component::ComponentId,
query::Access,
system::{BoxedSystem, IntoSystem, System, SystemId},
system::{IntoSystem, System},
world::World,
};
use bevy_utils::HashMap;
use std::borrow::Cow;

#[derive(Default)]
pub struct Schedule {
stages: HashMap<BoxedStageLabel, Box<dyn Stage>>,
stage_order: Vec<BoxedStageLabel>,
run_criteria: RunCriteria,
run_criteria: BoxedRunCriteria,
}

impl Schedule {
Expand Down Expand Up @@ -229,110 +229,3 @@ impl Stage for Schedule {
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShouldRun {
/// Yes, the system should run.
Yes,
/// No, the system should not run.
No,
/// Yes, the system should run, and afterwards the criteria should be checked again.
YesAndCheckAgain,
/// No, the system should not run right now, but the criteria should be checked again later.
NoAndCheckAgain,
}

pub(crate) struct RunCriteria {
criteria_system: Option<BoxedSystem<(), ShouldRun>>,
initialized: bool,
}

impl Default for RunCriteria {
fn default() -> Self {
Self {
criteria_system: None,
initialized: false,
}
}
}

impl RunCriteria {
pub fn set(&mut self, criteria_system: BoxedSystem<(), ShouldRun>) {
self.criteria_system = Some(criteria_system);
self.initialized = false;
}

pub fn should_run(&mut self, world: &mut World) -> ShouldRun {
if let Some(ref mut run_criteria) = self.criteria_system {
if !self.initialized {
run_criteria.initialize(world);
self.initialized = true;
}
let should_run = run_criteria.run((), world);
run_criteria.apply_buffers(world);
should_run
} else {
ShouldRun::Yes
}
}
}

pub struct RunOnce {
ran: bool,
system_id: SystemId,
archetype_component_access: Access<ArchetypeComponentId>,
component_access: Access<ComponentId>,
}

impl Default for RunOnce {
fn default() -> Self {
Self {
ran: false,
system_id: SystemId::new(),
archetype_component_access: Default::default(),
component_access: Default::default(),
}
}
}

impl System for RunOnce {
type In = ();
type Out = ShouldRun;

fn name(&self) -> Cow<'static, str> {
Cow::Borrowed(std::any::type_name::<RunOnce>())
}

fn id(&self) -> SystemId {
self.system_id
}

fn new_archetype(&mut self, _archetype: &Archetype) {}

fn archetype_component_access(&self) -> &Access<ArchetypeComponentId> {
&self.archetype_component_access
}

fn component_access(&self) -> &Access<ComponentId> {
&self.component_access
}

fn is_send(&self) -> bool {
true
}

unsafe fn run_unsafe(&mut self, _input: Self::In, _world: &World) -> Self::Out {
if self.ran {
ShouldRun::No
} else {
self.ran = true;
ShouldRun::Yes
}
}

fn apply_buffers(&mut self, _world: &mut World) {}

fn initialize(&mut self, _world: &mut World) {}

fn check_change_tick(&mut self, _change_tick: u32) {}
}
Loading