Skip to content
Open
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
19 changes: 19 additions & 0 deletions crates/bevy_ecs/src/schedule/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ pub enum ScheduleBuildWarning {
/// [`LogLevel::Error`]: crate::schedule::LogLevel::Error
#[error("Systems with conflicting access have indeterminate run order: {0:?}")]
Ambiguity(Vec<(SystemKey, SystemKey, Vec<ComponentId>)>),
/// The system set contains no systems.
///
/// This warning is **disabled** by default, but can be enabled by setting
/// [`ScheduleBuildSettings::empty_set_detection`] to `true`].
///
/// [`ScheduleBuildSettings::empty_set_detection`]: crate::schedule::ScheduleBuildSettings::empty_set_detection
#[error("The system set contains no systems: {0:?}")]
EmptySet(NodeId),
}

impl ScheduleBuildError {
Expand Down Expand Up @@ -156,6 +164,14 @@ impl ScheduleBuildError {
message
}

fn empty_set_to_string(node_id: &NodeId, graph: &ScheduleGraph) -> String {
format!(
"{} `{}` contains no systems",
node_id.kind(),
graph.get_node_name(node_id)
)
}

fn dependency_loop_to_string(node_id: &NodeId, graph: &ScheduleGraph) -> String {
format!(
"{} `{}` has been told to run before itself",
Expand Down Expand Up @@ -256,6 +272,9 @@ impl ScheduleBuildWarning {
ScheduleBuildWarning::Ambiguity(ambiguities) => {
ScheduleBuildError::ambiguity_to_string(ambiguities, graph, world.components())
}
ScheduleBuildWarning::EmptySet(node_id) => {
ScheduleBuildError::empty_set_to_string(node_id, graph)
}
}
}
}
82 changes: 82 additions & 0 deletions crates/bevy_ecs/src/schedule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,88 @@ mod tests {
));
}

#[test]
fn empty_set() {
let mut world = World::new();
let mut schedule = Schedule::default();

schedule.set_build_settings(ScheduleBuildSettings {
empty_set_detection: true,
..Default::default()
});

// Add `A`.
schedule.configure_sets(TestSystems::A);

_ = schedule.initialize(&mut world);
let warnings = schedule.warnings();

assert!(matches!(warnings[0], ScheduleBuildWarning::EmptySet(_)));
}

#[test]
fn empty_set_with_edges() {
let mut world = World::new();
let mut schedule = Schedule::default();

schedule.set_build_settings(ScheduleBuildSettings {
empty_set_detection: true,
..Default::default()
});

// Add `A`.
schedule.configure_sets(TestSystems::A.after(TestSystems::B));

_ = schedule.initialize(&mut world);
let warnings = schedule.warnings();

assert!(matches!(warnings[0], ScheduleBuildWarning::EmptySet(_)));
}

#[test]
fn empty_set_with_condition() {
let mut world = World::new();
let mut schedule = Schedule::default();

schedule.set_build_settings(ScheduleBuildSettings {
empty_set_detection: true,
..Default::default()
});

fn true_condition() -> bool {
true
}

// Add `A`.
schedule.configure_sets(TestSystems::A.run_if(true_condition));

_ = schedule.initialize(&mut world);
let warnings = schedule.warnings();

assert!(matches!(warnings[0], ScheduleBuildWarning::EmptySet(_)));
}

#[test]
fn empty_set_with_system() {
let mut world = World::new();
let mut schedule = Schedule::default();

schedule.set_build_settings(ScheduleBuildSettings {
empty_set_detection: true,
..Default::default()
});

fn system() {}

// Add `A`.
schedule.add_systems(system.in_set(TestSystems::A));

_ = schedule.initialize(&mut world);
let warnings = schedule.warnings();

assert!(warnings.is_empty());
}

#[test]
fn cross_dependency() {
let mut world = World::new();
Expand Down
17 changes: 15 additions & 2 deletions crates/bevy_ecs/src/schedule/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1000,8 +1000,11 @@ impl ScheduleGraph {

// map all system sets to their systems
// go in reverse topological order (bottom-up) for efficiency
let (set_systems, set_system_bitsets) =
self.map_sets_to_systems(&self.hierarchy.topsort, &self.hierarchy.graph);
let (set_systems, set_system_bitsets) = self.map_sets_to_systems(
&self.hierarchy.topsort,
&self.hierarchy.graph,
&mut warnings,
);
self.check_order_but_intersect(&dep_results.connected, &set_system_bitsets)?;

// check that there are no edges to system-type sets that have multiple instances
Expand Down Expand Up @@ -1058,6 +1061,7 @@ impl ScheduleGraph {
&self,
hierarchy_topsort: &[NodeId],
hierarchy_graph: &DiGraph<NodeId>,
warnings: &mut Vec<ScheduleBuildWarning>,
) -> (
HashMap<SystemSetKey, Vec<SystemKey>>,
HashMap<SystemSetKey, HashSet<SystemKey>>,
Expand Down Expand Up @@ -1089,6 +1093,10 @@ impl ScheduleGraph {
}
}

if self.settings.empty_set_detection && systems.is_empty() {
warnings.push(ScheduleBuildWarning::EmptySet(id));
}

set_systems.insert(set_key, systems);
set_system_sets.insert(set_key, system_set);
}
Expand Down Expand Up @@ -1719,6 +1727,10 @@ pub struct ScheduleBuildSettings {
///
/// Defaults to [`LogLevel::Warn`].
pub hierarchy_detection: LogLevel,
/// If set to `true`, warnings will be emitted if any system set contains no systems.
///
/// Defaults to `true`.
pub empty_set_detection: bool,
/// Auto insert [`ApplyDeferred`] systems into the schedule,
/// when there are [`Deferred`](crate::prelude::Deferred)
/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
Expand Down Expand Up @@ -1752,6 +1764,7 @@ impl ScheduleBuildSettings {
Self {
ambiguity_detection: LogLevel::Ignore,
hierarchy_detection: LogLevel::Warn,
empty_set_detection: false,
auto_insert_apply_deferred: true,
use_shortnames: true,
report_sets: true,
Expand Down
Loading