Skip to content

Add BeforeAdd and AfterRemove lifecycle events#22961

Open
jonas-meyer wants to merge 5 commits intobevyengine:mainfrom
jonas-meyer:feat/before-add-after-remove
Open

Add BeforeAdd and AfterRemove lifecycle events#22961
jonas-meyer wants to merge 5 commits intobevyengine:mainfrom
jonas-meyer:feat/before-add-after-remove

Conversation

@jonas-meyer
Copy link
Copy Markdown
Contributor

Objective

  • Add BeforeAdd and AfterRemove lifecycle events to complete the symmetric lifecycle: BeforeAdd -> Add -> Insert -> Discard -> Remove -> AfterRemove
  • Without these events, there is no point in the lifecycle where the entity is observable in the "component absent" state — Add fires after the component exists, and Remove fires before it's gone
  • These are prerequisites for query-based observers like DataAdded<Without> (Lifecycle event observers for queries #20817), which need to evaluate queries when the component is not yet/no longer present
  • Part of the structured lifecycle proposal (Make lifecycle events ordering and meaning more intuitive #20729)

Solution

Added before_add and after_remove as both component hooks and observer events, following the same patterns as existing lifecycle events:

  • BeforeAdd fires before Add and Insert. During insert, it fires before the archetype move (component data is not yet accessible). During spawn, it fires after data is written but before Add/Insert hooks.
  • AfterRemove fires after Remove. Component data has already been dropped and is not accessible. Fires during both remove() and despawn().

Note

BeforeAdd/AfterRemove are interim names. A future PR will introduce a Before<E>/After<E> wrapper scheme.

Testing

  • Extended 3 existing hook ordering tests to verify before_add fires at position 0 and after_remove fires at position 5 in the full lifecycle sequence
  • Added 7 new tests covering: replace skips before_add, component data inaccessibility during before_add/after_remove, entity inaccessibility during spawn's before_add, and observer-before-hook ordering for both events

Showcase

Click to view showcase
  // Component hooks
  world.register_component_hooks::<Health>()
      .before_add(|world, ctx| {
          // Fires before the component exists on the entity
          // Good for "about to gain component" logic
      })
      .after_remove(|world, ctx| {
          // Fires after the component is fully gone
          // Good for "component is now absent" cleanup
      });

  // Observers
  world.add_observer(|_: On<BeforeAdd, Health>, /* ... */| {
      // Entity does not yet have Health
  });

  world.add_observer(|_: On<AfterRemove, Health>, /* ... */| {
      // Entity no longer has Health
  });

  // Derive macro
  #[derive(Component)]
  #[component(before_add = on_before, after_remove = on_after)]
  struct Health(f32);

Copilot AI review requested due to automatic review settings February 14, 2026 21:43
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends bevy_ecs’s component lifecycle by introducing symmetric “component absent” observation points via the new BeforeAdd and AfterRemove lifecycle events, implemented both as component hooks and as observer-triggered events.

Changes:

  • Added BeforeAdd and AfterRemove lifecycle event types, event keys, and prelude exports.
  • Wired new lifecycle triggers into spawn/insert/remove/despawn paths, including new archetype flags and observer caching.
  • Expanded and added tests to validate hook/event ordering and inaccessibility semantics.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
crates/bevy_ecs/src/world/mod.rs Registers new lifecycle event keys during world initialization.
crates/bevy_ecs/src/world/entity_access/world_mut.rs Triggers AfterRemove during despawn after component data drop.
crates/bevy_ecs/src/world/deferred_world.rs Adds DeferredWorld helpers to run before_add / after_remove hooks.
crates/bevy_ecs/src/observer/centralized_storage.rs Adds cached observer buckets and archetype-flag mapping for the new events.
crates/bevy_ecs/src/lifecycle.rs Defines new event types/keys and adds hook registration APIs + documentation updates.
crates/bevy_ecs/src/lib.rs Exports BeforeAdd / AfterRemove in the public prelude.
crates/bevy_ecs/src/component/mod.rs Adds Component::before_add() / Component::after_remove() hook providers and docs.
crates/bevy_ecs/src/component/info.rs Sets new archetype hook flags based on component hook presence.
crates/bevy_ecs/src/component/constants.rs Reserves new constant component indices for lifecycle observer components.
crates/bevy_ecs/src/bundle/tests.rs Extends hook-order tests and adds new tests for new lifecycle semantics.
crates/bevy_ecs/src/bundle/spawner.rs Triggers BeforeAdd during spawn prior to writing component data.
crates/bevy_ecs/src/bundle/remove.rs Triggers AfterRemove after removal archetype move / data drop.
crates/bevy_ecs/src/bundle/insert.rs Triggers BeforeAdd for newly-added components before the archetype move.
crates/bevy_ecs/src/archetype.rs Adds archetype flags and helper methods for new hook/observer presence checks.
crates/bevy_ecs/macros/src/component.rs Extends derive macro parsing/codegen to support before_add and after_remove attributes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/bevy_ecs/src/world/entity_access/world_mut.rs Outdated
Comment thread crates/bevy_ecs/src/lifecycle.rs Outdated
Comment thread crates/bevy_ecs/src/lifecycle.rs
Comment thread crates/bevy_ecs/src/bundle/spawner.rs
@jonas-meyer jonas-meyer marked this pull request as draft February 14, 2026 22:13
@jonas-meyer jonas-meyer marked this pull request as ready for review February 16, 2026 17:42
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events X-Needs-SME This type of work requires an SME to approve it. S-Needs-Goal This should have a C-Goal and should not continue until it has one C-Feature A new feature, making something new possible S-Waiting-on-SME This is currently waiting for an SME to resolve something controversial labels Feb 16, 2026
@alice-i-cecile
Copy link
Copy Markdown
Member

I'm in favor of something here: I really don't think our existing setup is adequate or clear. This PR looks fine at first blush, but I would like to have a proper design effort before moving forward though.

@jonas-meyer
Copy link
Copy Markdown
Contributor Author

I've written up something here: #20729 (comment)

Is this what you had in mind? Happy to move it to an issue as well.

@cart cart added this to ECS Feb 17, 2026
@github-project-automation github-project-automation Bot moved this to Needs SME Triage in ECS Feb 17, 2026
@alice-i-cecile alice-i-cecile moved this from Needs SME Triage to SME Triaged in ECS Feb 20, 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-Needs-Goal This should have a C-Goal and should not continue until it has one S-Waiting-on-SME This is currently waiting for an SME to resolve something controversial X-Needs-SME This type of work requires an SME to approve it.

Projects

Status: SME Triaged

Development

Successfully merging this pull request may close these issues.

4 participants