Skip to content

Better ScheduleBuildError introspection and handling #20256

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 5 commits into from
Jul 24, 2025

Conversation

ItsDoot
Copy link
Contributor

@ItsDoot ItsDoot commented Jul 23, 2025

Objective

I want to pull out the ad-hoc graph manipulation and analysis functions into generalized reusable functions, but in doing so encounter some borrow-checker issues. This is because the graphs are mutably borrowed during the build process at the points where we need to render warning and error messages (which require full access to the ScheduleGraph).

Solution

So, lets defer message rendering until the consumer actually needs it:

  • Replaced Strings in ScheduleBuildError variants with just the NodeId/SystemKey/SystemSetKey.
  • Added ScheduleBuildError::to_string to render the messages as they were previously.
  • Added ScheduleBuildWarning, a subset of the error enum of just the possible warnings.
  • Collected warnings into a Vec and returned them from the build process, caching them in the Schedule and made accessible via Schedule::warnings. Additionally automatically warn!() these warnings after the schedule is built.
  • Exposed ScheduleGraph::get_node_name so external consumers can do any special rendering themselves.
  • Finally, moved ScheduleBuildError and ScheduleBuildWarning to their own files to help incrementally minimize the large schedule.rs module.

Testing

Reusing current tests.

@ItsDoot ItsDoot added A-ECS Entities, components, systems, and events 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-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jul 23, 2025
@ItsDoot ItsDoot added S-Needs-Review Needs reviewer attention (from anyone!) to move forward and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jul 23, 2025
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Good cleanup. The context about the borrow checker was really helpful in understanding why this is necessary.

/// Category of warnings encountered during [`Schedule::initialize`](crate::schedule::Schedule::initialize).
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ScheduleBuildWarning {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we just alias ScheduleBuildError as ScheduleBuildWarning? It doesn't feel like it deserves it's own type as it's just the difference in how the error is reported/handled.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually added it last minute expecting someone to say something along the lines of "well if only two of the variants can actually be warnings then there should be a separate enum". Happy to remove it, though!

Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, the error enum should have an ElevatedWarning variant that wraps these warnings. I think that would be best of both worlds.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't that make the type recursive and hence unsized?

Copy link
Contributor Author

@ItsDoot ItsDoot Jul 24, 2025

Choose a reason for hiding this comment

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

Added ScheduleBuildError::Elevated and removed the duplicate variants in ScheduleBuildError.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah I misunderstood what the suggestion was. I think I still prefer my solution as now you have to look in two places to see all the errors, but not going to block on it.

Copy link
Contributor

@ElliottjPierce ElliottjPierce left a comment

Choose a reason for hiding this comment

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

A couple of small ideas, but looks good to me! I don't know enough about how schedules are built to do a full correctness review, but the code quality looks good to me!

I have also been annoyed with the previous state because of borrow checking when I was working on queued component registration, so I'm very glad for this to be cleaned up!

self.initialize(world)
.unwrap_or_else(|e| panic!("Error when initializing schedule {:?}: {e}", self.label));
self.initialize(world).unwrap_or_else(|e| {
panic!(
Copy link
Contributor

Choose a reason for hiding this comment

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

This panic feels like it should be an error returned out, but same behavior as before, so I'm fine with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replacing panics with errors is always good, but I'll leave that for a small follow up PR.

impl ScheduleBuildError {
/// Renders the error as a human-readable string with node identifiers
/// replaced with their names.
pub fn to_string(&self, graph: &ScheduleGraph, world: &World) -> String {
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens when the wrong graph or world is passed in? I don't really care as long as it doesn't panic. And documenting what would happen would be good too IMO.

Copy link
Contributor

Choose a reason for hiding this comment

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

IDK, but another idea would be to keep a reference to the graph and world in the error types. That way, using the wrong world and graph would be impossible. As long as the reference is only tied in at the end, I don't think it would cause any borrow checking issues while calculating the errors.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What happens when the wrong graph or world is passed in? I don't really care as long as it doesn't panic. And documenting what would happen would be good too IMO.

It just leads to incorrect node names and component names; I added this as a note for the function.

IDK, but another idea would be to keep a reference to the graph and world in the error types. That way, using the wrong world and graph would be impossible. As long as the reference is only tied in at the end, I don't think it would cause any borrow checking issues while calculating the errors.

Eh, I think we've had annoyances in the past with error types that hold references.

/// Category of warnings encountered during [`Schedule::initialize`](crate::schedule::Schedule::initialize).
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ScheduleBuildWarning {
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, the error enum should have an ElevatedWarning variant that wraps these warnings. I think that would be best of both worlds.

/// Category of errors encountered during [`Schedule::initialize`](crate::schedule::Schedule::initialize).
#[non_exhaustive]
#[derive(Error, Debug)]
pub enum ScheduleBuildError {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this needs to change since it was like this before, but It sounds like multiple of these errors could happen at once. Like there could be a cross-dependency and a dependency loop I would guess. Not returning all errors could turn fixing them into a game of wack-a-mole. It might be nice to just have a struct that could represent all possible permutations of errors, like vecs of dependency loops, a bool for uninitialized, etc. IDK if that's actually a good idea; just something to think about.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I want to move in this direction at some point, but would rather let the larger changes surface first.

@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 23, 2025
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

I like the elevated warning variant. Nice!

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jul 24, 2025
Merged via the queue into bevyengine:main with commit 0090846 Jul 24, 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-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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants