bevyengine / bevyengine/bevy

StateScoped should support OnRemove observers

Open
#15,072 10 comments 1 reaction 0 assignees View on GitHub
A-ECS C-Usability D-Modest S-Ready-For-Implementation
Dominant language
Rust
Stars
48.2k
Forks
4.8k
Avg merge
3d 22h
Merged PRs (30d)
161

Description

## What problem does this solve or what need does it fill?

A `StateScoped(SomeState)` entity might have a component that triggers `OnRemove` observers. However, any side effects of those observers potentially race with cleanup systems that run in `OnExit(SomeState)`, since `StateScoped` cleanup happens in `StateTransitionSteps::ExitSchedules`.

Additionally, one potentially common side effect of `OnRemove` observers is emitting events that are handled in `SomeState`. It's very likely that most such events should be isolated to that state, and hence should be cleaned up in scope. Adding ergonomics here would be a win.

## What solution would you like?

- Move `StateScoped` cleanup to a new system set that runs before `StateTransitionSteps::ExitSchedules`.
- Add `.add_state_scoped_event(s: impl States)` extension to `App`. This should register the event for queue cleanup in a system set that runs after `StateScoped` entity cleanup and before `StateTransitionSteps::ExitSchedules`. Or on the other side (or both), it should run immediately before `StateTransitionSteps::EnterSchedules` when entering the target state.

## What alternative(s) have you considered?

None

## Additional context

- You can't easily spawn entities within an observer. This turns out to be advantageous for `OnRemove` handlers that run when `StateScoped` cleans stuff up because it means you can't easily spawn new `StateScoped` entities that will leak outside the state (Even though you may want to do so as part of `OnRemove` handling. For example I have a handler in my last jam game that runs when a `CollectableDrop` component is removed from any entity in order to spawn a new collectable where the dying entity was. I use an event to 'extract' the spawn command out of the observer.).
- There is some nuance to state-scoped events. If you have an event that is sent `OnRemove` for a `StateScoped(S)` entity, then the event will be naturally cleaned up without leaking back into the next `S` **if** the next `S` is not set for the tick immediately after the tick with the state that `S` exited into (since events only live for 2 ticks).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.