Skip to content

feat(bevy_ecs): add Severity metadata to BevyError#22201

Open
syszery wants to merge 1 commit intobevyengine:mainfrom
syszery:feat/bevy-error-severity
Open

feat(bevy_ecs): add Severity metadata to BevyError#22201
syszery wants to merge 1 commit intobevyengine:mainfrom
syszery:feat/bevy-error-severity

Conversation

@syszery
Copy link
Contributor

@syszery syszery commented Dec 19, 2025

Objective

As outlined in #20561, Bevy's default error handler currently panics on most errors. In some cases, this is overly strict—e.g., inserting a component into a despawned entity is harmless and could be handled more gracefully in library code.

This PR implements the idea suggested by @Freyja-moth: each BevyError now carries a Severity, and the ResultSeverityExt trait allows overriding it. These changes lay the foundation for error handlers that can log, ignore, or panic based on severity.

Happy to adjust based on feedback.

Solution

  • Added Severity enum.
  • Updated InnerBevyError to store a Severity, defaulting to Severity::Critical to preserve existing panic-on-error behavior.
  • BevyError exposes severity() and with_severity() methods.
  • ResultSeverityExt provides ergonomic overriding of error severity on Result<T, E>.

Testing

  • Tests in the crate bevy_ecs pass (with the debug feature enabled); existing "should panic" tests continue to behave as expected.

Future TODOs

  • This primarily sets up the API; proper handling of severity in the default handler is left for future work.
  • Feedback on the API design and suggestions for integrating severity into the default handler are welcome.

@Freyja-moth
Copy link
Contributor

To add to the future work section, I do feel like it's a good idea to also add the ErrorSeverity trait as @alice-i-cecile outlined, as otherwise we've only solved the issue for error types that user interact with.

With just the error_severity field implemented errors that occur in commands (such as inserting an component into a despawned entity as you noted) are still just as strict unless the unwieldy queue_handled method is used.

@NthTensor NthTensor self-requested a review December 21, 2025 14:50
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
/// The error can be safely ignored.
Ignore,
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer to have Debug here as well, for errors that are lower severity than warnings but may still need to be surfaced during debugging.

Copy link
Contributor

@NthTensor NthTensor left a comment

Choose a reason for hiding this comment

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

This is pretty much what I was envisioning, nice work! Obviously there's more to do, but I'm happy with this.

@syszery
Copy link
Contributor Author

syszery commented Dec 21, 2025

Thanks for the feedback!

@NthTensor: I’ll add Debug to the severity levels in a follow-up commit.

@Freyja-moth: I understand your concerns—thanks for pointing this out! The preferred default for error severity should be None, right? And then the idea is to add the option to override errors not directly controlled by the user with the trait suggested by @alice-i-cecile?

@Freyja-moth
Copy link
Contributor

The idea is to have the trait be the default with the option to override it

trait ErrorSeverity: Error {
    fn severity(&self) -> SeverityLevel;
}

pub struct BevyError {
    error: Box<dyn ErrorSeverity>,
    severity: Option<SeverityLevel>
}
impl BevyError {
    pub fn new(error: impl ErrorSeverity) -> Self {
        Self {
            error: Box::new(error),
            severity: None,
        }
    }
    pub fn with_severity(&mut self, severity: SeverityLevel) {
        self.severity.replace(severity);
    }
    pub fn severity(&self) -> SeverityLevel {
        self.severity.unwrap_or(self.error.severity())
    }
}

@syszery
Copy link
Contributor Author

syszery commented Dec 21, 2025

I didn’t modify the BevyError directly because of the comment above InnerBevyError. I understand that if we use a dyn Trait or add the severity field directly to BevyError, we would lose the "thin pointer" benefit.

When thinking more about using Option<Severity>, I‘m in doubt if this really solves the issue. If the severity is None, it leaves the handler in an ambiguous state.

The problem seems to be: What is the right default? With Critical as the default, we ensure that errors are always visible and addressed right away—nothing is left unhandled. This is why I thought that a clear, strong default might be safe. But it is also extreme and the other (equally extreme) option would be Ignore.

@Freyja-moth
Copy link
Contributor

Freyja-moth commented Dec 21, 2025

Yes sorry, I'm not familiar with the internals of BevyError. In this case it would be updating InnerBevyError to look like the BevyError shown above.

When thinking more about using Option, I‘m in doubt if this really solves the issue. If the severity is None, it leaves the handler in an ambiguous state.

It wouldn't, as the error severity would default to the error severity as specified by the trait.

To give an example

pub struct EntityDoesNotMatchError;
impl ErrorSeverity for EntityDoesNotMatchError {
    fn severity() -> SeverityLevel {
        SeverityLevel::Critical
    }
}

fn tester() {
    let mut result = Err(BevyError::new(EntityDoesNotMatchError)); // `BevyError::severity` currently returns SeverityLevel::Critical since `EntityDoesNotMatchError::severity` returns `SeverityLevel::Critical` and `InnerBevyError.severity` is `None`

    result.with_severity(SeverityLevel::Ignore); // `BevyError::severity` currently returns SeverityLevel::Ignore, since `InnerBevyError.severity` is now no longer `None` and is used instead of `EntityDoesNotMatchError::severity`
}

@Freyja-moth
Copy link
Contributor

Freyja-moth commented Dec 21, 2025

Looking into it further doing this would introduce issues with non bevy errors as they wouldn't be able to be stored.

There's currently no way to do what I'd suggested while allowing non bevy errors, but once specialization is added we should be able to add a From<E> for InnerBevyError where E: ErrorSeverity and set the severity field to default to the severity as specified by the trait

@NthTensor
Copy link
Contributor

We should not design anything that assumes specialization, it may never be implemented. I'll try to review the this discussion in more detail this week.

@IQuick143 IQuick143 added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Dec 23, 2025
@syszery
Copy link
Contributor Author

syszery commented Jan 31, 2026

Hi @NthTensor, just checking in on this PR. Do you have any feedback or a decision on the changes?

Comment on lines +113 to +114
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Severity {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should it implement Ord?

@cart cart added this to ECS Feb 12, 2026
@github-project-automation github-project-automation bot moved this to Needs SME Triage in ECS Feb 12, 2026
@alice-i-cecile alice-i-cecile added the S-Needs-Goal This should have a C-Goal and should not continue until it has one label Feb 12, 2026
@alice-i-cecile alice-i-cecile moved this from Needs SME Triage to SME Triaged in ECS Feb 12, 2026
Copy link
Contributor

@AlephCubed AlephCubed left a comment

Choose a reason for hiding this comment

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

I agree with the previous comments on implementing Ord and a Debug case for Severity. But other then that I think it looks good.

@NthTensor NthTensor 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 Mar 17, 2026
@alice-i-cecile alice-i-cecile removed the S-Needs-Goal This should have a C-Goal and should not continue until it has one label Mar 17, 2026
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-Feature A new feature, making something new possible S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

Status: SME Triaged

Development

Successfully merging this pull request may close these issues.

7 participants