Skip to content

Simplify self-edge checking in schedule building #20015

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
merged 2 commits into from
Jul 8, 2025

Conversation

chescock
Copy link
Contributor

@chescock chescock commented Jul 7, 2025

Objective

Make the schedule graph code more understandable, and replace some panics with Results.

I found the check_edges and check_hierarchy functions a little confusing, as they combined two concerns: Initializing graph nodes for system sets, and checking for self-edges on system sets. It was hard to understand the self-edge checks because it wasn't clear what NodeId was being checked against! So, let's separate those concerns, and move them to more appropriate locations.

Fix a bug where schedule.configure_sets((SameSet, SameSet).chain()); would panic with an unhelpful message: assertion failed: index_a < index_b.

Solution

Remove the check_edges and check_hierarchy functions, separating the initialization behavior and the checking behavior and moving them where they are easier to understand.

For initializing graph nodes, do this on-demand using the entry API by replacing later self.system_set_ids[&set] calls with a self.system_sets.get_or_add_set(set) method. This should avoid the need for an extra pass over the graph and an extra lookup.

Unfortunately, implementing that method directly on ScheduleGraph leads to borrowing errors as it borrows the entire struct. So, split out the collections managing system sets into a separate struct.

For checking self-edges, move this check later so that it can be reported by returning a Result from Schedule::initialize instead of having to panic in configure_set_inner. The issue was that iter_sccs does not report self-edges as cycles, since the resulting components only have one node, but that later code assumes all edges point forward. So, check for self-edges directly, immediately before calling iter_sccs.

This also ensures we catch every way that self-edges can be added. The previous code missed an edge case where chain()ing a set to itself would create a self-edge and would trigger a debug_assert.

Create system set nodes on-demand so we don't need a second pass to check for them.
Check for self edges in `topsort_graph` so that we can allow them through without panicking elsewhere.
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 7, 2025
@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior X-Uncontroversial This work is generally agreed upon D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes labels Jul 7, 2025
@@ -826,7 +841,7 @@ impl ScheduleGraph {

/// Returns `true` if the given system set is part of the graph. Otherwise, returns `false`.
pub fn contains_set(&self, set: impl SystemSet) -> bool {
self.system_set_ids.contains_key(&set.intern())
self.system_sets.system_set_ids.contains_key(&set.intern())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, candidate for a follow up: The self.system_sets.system_setxxx stutter is mildly annoying; it would be nice to add functions on SystemSets that forwards to these.

@@ -760,6 +760,27 @@ enum UninitializedId {
},
}

/// Metadata for system sets in a schedule.
#[derive(Default)]
struct SystemSets {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design I have in mind for generalizing schedules includes splitting this out, glad you've taken the step for me :)

@@ -1833,6 +1783,17 @@ impl ScheduleGraph {
graph: &DiGraph,
report: ReportCycles,
) -> Result<Vec<NodeId>, ScheduleBuildError> {
// Check explicitly for self-edges.
// `iter_sccs` won't report them as cycles because they still form components of one node.
if let Some((node, _)) = graph.all_edges().find(|(left, right)| left == right) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, candidate for a follow up: We should report all self-edges rather than just the first one encountered.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking, candidate for a follow up: We should report all self-edges rather than just the first one encountered.

That is a good idea, but I'm not feeling enthusiastic about implementing it, so I'll just offer to review it if someone else does :).

Copy link
Contributor

@hymm hymm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think moving the check from adding the sets to schedule initialization time makes sense. Having the schedule errors all come from one place is simpler. I made one suggestion for renaming the fields, but is non blocking.

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jul 8, 2025
@alice-i-cecile
Copy link
Member

@chescock, let me know your call on the rename suggestions, then I'll do a final pass before merging.

@alice-i-cecile alice-i-cecile enabled auto-merge July 8, 2025 17:52
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jul 8, 2025
Merged via the queue into bevyengine:main with commit 0ee9377 Jul 8, 2025
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Bug An unexpected or incorrect behavior C-Code-Quality A section of code that is hard to understand or change D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants