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

Descriptive error message for circular required components recursion #16648

Conversation

SpecificProtagonist
Copy link
Contributor

@SpecificProtagonist SpecificProtagonist commented Dec 4, 2024

Objective

Fixes #16645

Solution

Keep track of components in callstack when registering required components.

Testing

Added a test checking that the error fires.


Showcase

#[derive(Component, Default)]
#[require(B)]
struct A;

#[derive(Component, Default)]
#[require(A)]
struct B;
World::new().spawn(A);
thread 'main' panicked at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/component.rs:415:13:
Recursive required components detected: A → B → A

@AlexAegis
Copy link

Why not just ignore the circle and just stop the recursion when encountering one already processed? Is there a reason why it must be treated as a hard error?

@SpecificProtagonist
Copy link
Contributor Author

SpecificProtagonist commented Dec 5, 2024

Because a circular requirement is likely unintentional (such as in the linked issue) – if multiple components require each other, they should be a single component, and there is no reason for a component to directly require itself.

@AlexAegis
Copy link

I hadn't thought of that! Maybe it would be worth mentioning in the error message that "maybe you want to merge these two components into one?"

@SpecificProtagonist
Copy link
Contributor Author

Good point, added.

@IQuick143 IQuick143 added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Dec 6, 2024
) {
if let Some((&requiree, check)) = recursion_check_stack.split_last() {
if let Some(direct_recursion) = check
.iter()
Copy link
Contributor

Choose a reason for hiding this comment

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

Quadratic algorithms always make me nervous. Would it make sense to pass a FixedBitSet of ComponentIds alongside the Vec so that this can be an O(1) check? Or would the overhead of updating a second structure make it slower in realistic cases?

Copy link
Contributor Author

@SpecificProtagonist SpecificProtagonist Dec 7, 2024

Choose a reason for hiding this comment

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

I don't think we'll see deep enough chains of required components where the lookup in the vec has a measurable perf impact. The depth will be low, and comparisons with a list of integers that laid out successively in memory is really fast so the constant factor is very small.

For a depth of 50 components depending on another in a chain – much deeper than what we should see – doing all of these checks for all of the components took a combined total of 200µs on my laptop.

Still, while I don't think it's necessary, adding a FixedBitSet check shouldn't make the code much more complicated so I'd be ok with doing that if you want.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think you're right that the chains will be small, so an extra check wouldn't be worth the complexity.

) {
#bevy_ecs_path::component::enforce_no_required_components_recursion(components, recursion_check_stack);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think doing this check before the push means you have one more level of recursion than is necessary.

Like, if you register a component that requires itself, then the first time this is called the stack will be empty, and the second time it will have one element that it binds to requiree but an empty check list. So it will pass on the second call, even though it had enough information to detect the error!

The simplest thing to do might be to pass self_id to enforce_no_required_components_recursion so that it doesn't need to do split_last().

Copy link
Contributor Author

@SpecificProtagonist SpecificProtagonist Dec 7, 2024

Choose a reason for hiding this comment

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

Performance wise the extra recursion doesn't matter, as it's only in the panic case (unless I've misunderstood something).

Wouldn't make the code simpler either, as I would then have to append the self_id if the check fails so the full cycle is printed.

crates/bevy_ecs/src/component.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/component.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/lib.rs Outdated Show resolved Hide resolved
) {
if let Some((&requiree, check)) = recursion_check_stack.split_last() {
if let Some(direct_recursion) = check
.iter()
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think you're right that the chains will be small, so an extra check wouldn't be worth the complexity.

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.

Great comments :) It's good to improve the messages here.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Dec 11, 2024
@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 Dec 11, 2024
Merged via the queue into bevyengine:main with commit 5f1e114 Dec 11, 2024
29 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-Usability A targeted quality-of-life change that makes Bevy easier to use 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.

Cycles in required components produce inscrutable error messages
5 participants