560429ebd9
68 Commits
Author | SHA1 | Message | Date | |
---|---|---|---|---|
![]() |
560429ebd9
|
Observer trigger refactor (#19935)
# Objective - The usage of ComponentId is quite confusing: events are not components. By newtyping this, we can prevent stupid mistakes, avoid leaking internal details and make the code clearer for users and engine devs reading it. - Adopts https://github.com/bevyengine/bevy/pull/19755 --------- Co-authored-by: oscar-benderstone <oscarbenderstone@gmail.com> Co-authored-by: Oscar Bender-Stone <88625129+oscar-benderstone@users.noreply.github.com> |
||
![]() |
efd17f133d
|
Rename num_entities to entity_count (#19781)
As discussed in https://github.com/bevyengine/bevy/pull/19780#issuecomment-2994554024. |
||
![]() |
61a5a37584
|
Improve module structure of observers code (#19779)
# Objective While working on #17607, I found myself confused and frustrated by the tangled web woven by the various modules inside of our observers code. Rather than tackle that as part of a big rewrite PR, I've decided to do the mature (if frustrating) thing where you split out your trivial but noisy refactoring first. There are a large number of moving parts, especially in terms of storage, and these are strewn willy-nilly across the module with no apparent ordering. To make matters worse, this was almost all just dumped into a multi-thousand LOC mod.rs at the root. ## Solution I've reshuffled the modules, attempting to: - reduce the size of the mod.rs file - organize structs so that smaller structs are found after the larger structs that contain them - group related functionality together - document why modules exist, and their broad organization No functional changes have been made here, although I've had to increase the visibility of a few fields from private to pub(crate) or pub(super) to keep things compiling. During these changes, I've opted for the lazy private module, public re-export strategy, to avoid causing any breakages, both within and outside of `bevy` itself. I think we can do better, but I want to leave that for a proper cleanup pass at the end. There's no sense maintaining migration guides and forcing multiple breaking changes throughout the cycle. ## Testing No functional changes; relying on existing test suite and the Rust compiler. |
||
![]() |
6dbe3600ed
|
Add num_entities() to World (#19780)
# Objective There is a lot of `world.entities().len()`, especially in tests. In tests, usually, the assumption is made that empty worlds do not contain any entities. This is about to change (#19711), and as such all of these tests are failing for that PR. ## Solution `num_entities` is a convenience method that returns the number of entities inside a world. It can later be adapted to exclude 'unexpected' entities, associated with internal data structures such as Resources, Queries, Systems. In general I argue for a separation of concepts where `World` ignores internal entities in methods such as `iter_entities()` and `clear_entities()`, that discussion is, however, separate from this PR. ## Testing I replaced most occurrences of `world.entities().len()` with `world.num_entities()` and the tests passed. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
7237c2173b
|
Make observers metadata / storage publicly visible (#19608)
# Objective Our strategy for storing observers is made up of several moving parts, which are ultimately fairly simple nested HashMaps. These types are currently `pub`, but lack any meaningful way to access this data. We have three options here: 1. Make these internals not `pub` at all. 2. Make the data read-only accessible. 3. Make the data mutably accessible. ## Solution I've opted for option 2, exposing read-only values. This is consistent with our existing approach to the ECS internals, allowing for easier debugging without risking wanton data corruption. If one day you would like to mutably access this data, please open an issue clearly explaining what you're trying to do. This was a pretty mechanical change, exposing fields via getters. I've also opted to do my best to clarify some field names and documentation: please double-check those for correctness. It was hard to be fully confident, as the field names and documentation was not very clear ;) ## Testing I spent some time going through the code paths, making sure that users can trace all the way from `World` to the leaf nodes. Reviewers, please ensure the same! ## Notes for reviewers This is part of a broader observer overhaul: I fully expect us to change up these internals and break these shiny new APIs. Probably even within the same cycle! But clean up your work area first: this sort of read-only getter and improved docs will be important to replace as we work. |
||
![]() |
f7e112a3c9
|
Let query items borrow from query state to avoid needing to clone (#15396)
# Objective Improve the performance of `FilteredEntity(Ref|Mut)` and `Entity(Ref|Mut)Except`. `FilteredEntityRef` needs an `Access<ComponentId>` to determine what components it can access. There is one stored in the query state, but query items cannot borrow from the state, so it has to `clone()` the access for each row. Cloning the access involves memory allocations and can be expensive. ## Solution Let query items borrow from their query state. Add an `'s` lifetime to `WorldQuery::Item` and `WorldQuery::Fetch`, similar to the one in `SystemParam`, and provide `&'s Self::State` to the fetch so that it can borrow from the state. Unfortunately, there are a few cases where we currently return query items from temporary query states: the sorted iteration methods create a temporary state to query the sort keys, and the `EntityRef::components<Q>()` methods create a temporary state for their query. To allow these to continue to work with most `QueryData` implementations, introduce a new subtrait `ReleaseStateQueryData` that converts a `QueryItem<'w, 's>` to `QueryItem<'w, 'static>`, and is implemented for everything except `FilteredEntity(Ref|Mut)` and `Entity(Ref|Mut)Except`. `#[derive(QueryData)]` will generate `ReleaseStateQueryData` implementations that apply when all of the subqueries implement `ReleaseStateQueryData`. This PR does not actually change the implementation of `FilteredEntity(Ref|Mut)` or `Entity(Ref|Mut)Except`! That will be done as a follow-up PR so that the changes are easier to review. I have pushed the changes as chescock/bevy#5. ## Testing I ran performance traces of many_foxes, both against main and against chescock/bevy#5, both including #15282. These changes do appear to make generalized animation a bit faster: (Red is main, yellow is chescock/bevy#5)  ## Migration Guide The `WorldQuery::Item` and `WorldQuery::Fetch` associated types and the `QueryItem` and `ROQueryItem` type aliases now have an additional lifetime parameter corresponding to the `'s` lifetime in `Query`. Manual implementations of `WorldQuery` will need to update the method signatures to include the new lifetimes. Other uses of the types will need to be updated to include a lifetime parameter, although it can usually be passed as `'_`. In particular, `ROQueryItem` is used when implementing `RenderCommand`. Before: ```rust fn render<'w>( item: &P, view: ROQueryItem<'w, Self::ViewQuery>, entity: Option<ROQueryItem<'w, Self::ItemQuery>>, param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult; ``` After: ```rust fn render<'w>( item: &P, view: ROQueryItem<'w, '_, Self::ViewQuery>, entity: Option<ROQueryItem<'w, '_, Self::ItemQuery>>, param: SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult; ``` --- Methods on `QueryState` that take `&mut self` may now result in conflicting borrows if the query items capture the lifetime of the mutable reference. This affects `get()`, `iter()`, and others. To fix the errors, first call `QueryState::update_archetypes()`, and then replace a call `state.foo(world, param)` with `state.query_manual(world).foo_inner(param)`. Alternately, you may be able to restructure the code to call `state.query(world)` once and then make multiple calls using the `Query`. Before: ```rust let mut state: QueryState<_, _> = ...; let d1 = state.get(world, e1); let d2 = state.get(world, e2); // Error: cannot borrow `state` as mutable more than once at a time println!("{d1:?}"); println!("{d2:?}"); ``` After: ```rust let mut state: QueryState<_, _> = ...; state.update_archetypes(world); let d1 = state.get_manual(world, e1); let d2 = state.get_manual(world, e2); // OR state.update_archetypes(world); let d1 = state.query(world).get_inner(e1); let d2 = state.query(world).get_inner(e2); // OR let query = state.query(world); let d1 = query.get_inner(e1); let d1 = query.get_inner(e2); println!("{d1:?}"); println!("{d2:?}"); ``` |
||
![]() |
b7d2cb8547
|
Provide access to the original target of entity-events in observers (#19663)
# Objective Getting access to the original target of an entity-event is really helpful when working with bubbled / propagated events. `bevy_picking` special-cases this, but users have requested this for all sorts of bubbled events. The existing naming convention was also very confusing. Fixes https://github.com/bevyengine/bevy/issues/17112, but also see #18982. ## Solution 1. Rename `ObserverTrigger::target` -> `current_target`. 1. Store `original_target: Option<Entity>` in `ObserverTrigger`. 1. Wire it up so this field gets set correctly. 1. Remove the `target` field on the `Pointer` events from `bevy_picking`. Closes https://github.com/bevyengine/bevy/pull/18710, which attempted the same thing. Thanks @emfax! ## Testing I've modified an existing test to check that the entities returned during event bubbling / propagation are correct. ## Notes to reviewers It's a little weird / sad that you can no longer access this infromation via the buffered events for `Pointer`. That said, you already couldn't access any bubbled target. We should probably remove the `BufferedEvent` form of `Pointer` to reduce confusion and overhead, but I didn't want to do so here. Observer events can be trivially converted into buffered events (write an observer with an EventWriter), and I suspect that that is the better migration if you want the controllable timing or performance characteristics of buffered events for your specific use case. ## Future work It would be nice to not store this data at all (and not expose any methods) if propagation was disabled. That involves more trait shuffling, and I don't think we should do it here for reviewability. --------- Co-authored-by: Joona Aalto <jondolf.dev@gmail.com> |
||
![]() |
38c3423693
|
Event Split: Event , EntityEvent , and BufferedEvent (#19647)
# Objective Closes #19564. The current `Event` trait looks like this: ```rust pub trait Event: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` The `Event` trait is used by both buffered events (`EventReader`/`EventWriter`) and observer events. If they are observer events, they can optionally be targeted at specific `Entity`s or `ComponentId`s, and can even be propagated to other entities. However, there has long been a desire to split the trait semantically for a variety of reasons, see #14843, #14272, and #16031 for discussion. Some reasons include: - It's very uncommon to use a single event type as both a buffered event and targeted observer event. They are used differently and tend to have distinct semantics. - A common footgun is using buffered events with observers or event readers with observer events, as there is no type-level error that prevents this kind of misuse. - #19440 made `Trigger::target` return an `Option<Entity>`. This *seriously* hurts ergonomics for the general case of entity observers, as you need to `.unwrap()` each time. If we could statically determine whether the event is expected to have an entity target, this would be unnecessary. There's really two main ways that we can categorize events: push vs. pull (i.e. "observer event" vs. "buffered event") and global vs. targeted: | | Push | Pull | | ------------ | --------------- | --------------------------- | | **Global** | Global observer | `EventReader`/`EventWriter` | | **Targeted** | Entity observer | - | There are many ways to approach this, each with their tradeoffs. Ultimately, we kind of want to split events both ways: - A type-level distinction between observer events and buffered events, to prevent people from using the wrong kind of event in APIs - A statically designated entity target for observer events to avoid accidentally using untargeted events for targeted APIs This PR achieves these goals by splitting event traits into `Event`, `EntityEvent`, and `BufferedEvent`, with `Event` being the shared trait implemented by all events. ## `Event`, `EntityEvent`, and `BufferedEvent` `Event` is now a very simple trait shared by all events. ```rust pub trait Event: Send + Sync + 'static { // Required for observer APIs fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` You can call `trigger` for *any* event, and use a global observer for listening to the event. ```rust #[derive(Event)] struct Speak { message: String, } // ... app.add_observer(|trigger: On<Speak>| { println!("{}", trigger.message); }); // ... commands.trigger(Speak { message: "Y'all like these reworked events?".to_string(), }); ``` To allow an event to be targeted at entities and even propagated further, you can additionally implement the `EntityEvent` trait: ```rust pub trait EntityEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This lets you call `trigger_targets`, and to use targeted observer APIs like `EntityCommands::observe`: ```rust #[derive(Event, EntityEvent)] #[entity_event(traversal = &'static ChildOf, auto_propagate)] struct Damage { amount: f32, } // ... let enemy = commands.spawn((Enemy, Health(100.0))).id(); // Spawn some armor as a child of the enemy entity. // When the armor takes damage, it will bubble the event up to the enemy. let armor_piece = commands .spawn((ArmorPiece, Health(25.0), ChildOf(enemy))) .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| { // Note: `On::target` only exists because this is an `EntityEvent`. let mut health = query.get(trigger.target()).unwrap(); health.0 -= trigger.amount(); }); commands.trigger_targets(Damage { amount: 10.0 }, armor_piece); ``` > [!NOTE] > You *can* still also trigger an `EntityEvent` without targets using `trigger`. We probably *could* make this an either-or thing, but I'm not sure that's actually desirable. To allow an event to be used with the buffered API, you can implement `BufferedEvent`: ```rust pub trait BufferedEvent: Event {} ``` The event can then be used with `EventReader`/`EventWriter`: ```rust #[derive(Event, BufferedEvent)] struct Message(String); fn write_hello(mut writer: EventWriter<Message>) { writer.write(Message("I hope these examples are alright".to_string())); } fn read_messages(mut reader: EventReader<Message>) { // Process all buffered events of type `Message`. for Message(message) in reader.read() { println!("{message}"); } } ``` In summary: - Need a basic event you can trigger and observe? Derive `Event`! - Need the event to be targeted at an entity? Derive `EntityEvent`! - Need the event to be buffered and support the `EventReader`/`EventWriter` API? Derive `BufferedEvent`! ## Alternatives I'll now cover some of the alternative approaches I have considered and briefly explored. I made this section collapsible since it ended up being quite long :P <details> <summary>Expand this to see alternatives</summary> ### 1. Unified `Event` Trait One option is not to have *three* separate traits (`Event`, `EntityEvent`, `BufferedEvent`), and to instead just use associated constants on `Event` to determine whether an event supports targeting and buffering or not: ```rust pub trait Event: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; const TARGETED: bool = false; const BUFFERED: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` Methods can then use bounds like `where E: Event<TARGETED = true>` or `where E: Event<BUFFERED = true>` to limit APIs to specific kinds of events. This would keep everything under one `Event` trait, but I don't think it's necessarily a good idea. It makes APIs harder to read, and docs can't easily refer to specific types of events. You can also create weird invariants: what if you specify `TARGETED = false`, but have `Traversal` and/or `AUTO_PROPAGATE` enabled? ### 2. `Event` and `Trigger` Another option is to only split the traits between buffered events and observer events, since that is the main thing people have been asking for, and they have the largest API difference. If we did this, I think we would need to make the terms *clearly* separate. We can't really use `Event` and `BufferedEvent` as the names, since it would be strange that `BufferedEvent` doesn't implement `Event`. Something like `ObserverEvent` and `BufferedEvent` could work, but it'd be more verbose. For this approach, I would instead keep `Event` for the current `EventReader`/`EventWriter` API, and call the observer event a `Trigger`, since the "trigger" terminology is already used in the observer context within Bevy (both as a noun and a verb). This is also what a long [bikeshed on Discord](https://discord.com/channels/691052431525675048/749335865876021248/1298057661878898791) seemed to land on at the end of last year. ```rust // For `EventReader`/`EventWriter` pub trait Event: Send + Sync + 'static {} // For observers pub trait Trigger: Send + Sync + 'static { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; const TARGETED: bool = false; fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } ``` The problem is that "event" is just a really good term for something that "happens". Observers are rapidly becoming the more prominent API, so it'd be weird to give them the `Trigger` name and leave the good `Event` name for the less common API. So, even though a split like this seems neat on the surface, I think it ultimately wouldn't really work. We want to keep the `Event` name for observer events, and there is no good alternative for the buffered variant. (`Message` was suggested, but saying stuff like "sends a collision message" is weird.) ### 3. `GlobalEvent` + `TargetedEvent` What if instead of focusing on the buffered vs. observed split, we *only* make a distinction between global and targeted events? ```rust // A shared event trait to allow global observers to work pub trait Event: Send + Sync + 'static { fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } // For buffered events and non-targeted observer events pub trait GlobalEvent: Event {} // For targeted observer events pub trait TargetedEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This is actually the first approach I implemented, and it has the neat characteristic that you can only use non-targeted APIs like `trigger` with a `GlobalEvent` and targeted APIs like `trigger_targets` with a `TargetedEvent`. You have full control over whether the entity should or should not have a target, as they are fully distinct at the type-level. However, there's a few problems: - There is no type-level indication of whether a `GlobalEvent` supports buffered events or just non-targeted observer events - An `Event` on its own does literally nothing, it's just a shared trait required to make global observers accept both non-targeted and targeted events - If an event is both a `GlobalEvent` and `TargetedEvent`, global observers again have ambiguity on whether an event has a target or not, undermining some of the benefits - The names are not ideal ### 4. `Event` and `EntityEvent` We can fix some of the problems of Alternative 3 by accepting that targeted events can also be used in non-targeted contexts, and simply having the `Event` and `EntityEvent` traits: ```rust // For buffered events and non-targeted observer events pub trait Event: Send + Sync + 'static { fn register_component_id(world: &mut World) -> ComponentId { ... } fn component_id(world: &World) -> Option<ComponentId> { ... } } // For targeted observer events pub trait EntityEvent: Event { type Traversal: Traversal<Self>; const AUTO_PROPAGATE: bool = false; } ``` This is essentially identical to this PR, just without a dedicated `BufferedEvent`. The remaining major "problem" is that there is still zero type-level indication of whether an `Event` event *actually* supports the buffered API. This leads us to the solution proposed in this PR, using `Event`, `EntityEvent`, and `BufferedEvent`. </details> ## Conclusion The `Event` + `EntityEvent` + `BufferedEvent` split proposed in this PR aims to solve all the common problems with Bevy's current event model while keeping the "weirdness" factor minimal. It splits in terms of both the push vs. pull *and* global vs. targeted aspects, while maintaining a shared concept for an "event". ### Why I Like This - The term "event" remains as a single concept for all the different kinds of events in Bevy. - Despite all event types being "events", they use fundamentally different APIs. Instead of assuming that you can use an event type with any pattern (when only one is typically supported), you explicitly opt in to each one with dedicated traits. - Using separate traits for each type of event helps with documentation and clearer function signatures. - I can safely make assumptions on expected usage. - If I see that an event is an `EntityEvent`, I can assume that I can use `observe` on it and get targeted events. - If I see that an event is a `BufferedEvent`, I can assume that I can use `EventReader` to read events. - If I see both `EntityEvent` and `BufferedEvent`, I can assume that both APIs are supported. In summary: This allows for a unified concept for events, while limiting the different ways to use them with opt-in traits. No more guess-work involved when using APIs. ### Problems? - Because `BufferedEvent` implements `Event` (for more consistent semantics etc.), you can still use all buffered events for non-targeted observers. I think this is fine/good. The important part is that if you see that an event implements `BufferedEvent`, you know that the `EventReader`/`EventWriter` API should be supported. Whether it *also* supports other APIs is secondary. - I currently only support `trigger_targets` for an `EntityEvent`. However, you can technically target components too, without targeting any entities. I consider that such a niche and advanced use case that it's not a huge problem to only support it for `EntityEvent`s, but we could also split `trigger_targets` into `trigger_entities` and `trigger_components` if we wanted to (or implement components as entities :P). - You can still trigger an `EntityEvent` *without* targets. I consider this correct, since `Event` implements the non-targeted behavior, and it'd be weird if implementing another trait *removed* behavior. However, it does mean that global observers for entity events can technically return `Entity::PLACEHOLDER` again (since I got rid of the `Option<Entity>` added in #19440 for ergonomics). I think that's enough of an edge case that it's not a huge problem, but it is worth keeping in mind. - ~~Deriving both `EntityEvent` and `BufferedEvent` for the same type currently duplicates the `Event` implementation, so you instead need to manually implement one of them.~~ Changed to always requiring `Event` to be derived. ## Related Work There are plans to implement multi-event support for observers, especially for UI contexts. [Cart's example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508) API looked like this: ```rust // Truncated for brevity trigger: Trigger<( OnAdd<Pressed>, OnRemove<Pressed>, OnAdd<InteractionDisabled>, OnRemove<InteractionDisabled>, OnInsert<Hovered>, )>, ``` I believe this shouldn't be in conflict with this PR. If anything, this PR might *help* achieve the multi-event pattern for entity observers with fewer footguns: by statically enforcing that all of these events are `EntityEvent`s in the context of `EntityCommands::observe`, we can avoid misuse or weird cases where *some* events inside the trigger are targeted while others are not. |
||
![]() |
e5dc177b4b
|
Rename Trigger to On (#19596)
# Objective Currently, the observer API looks like this: ```rust app.add_observer(|trigger: Trigger<Explode>| { info!("Entity {} exploded!", trigger.target()); }); ``` Future plans for observers also include "multi-event observers" with a trigger that looks like this (see [Cart's example](https://github.com/bevyengine/bevy/issues/14649#issuecomment-2960402508)): ```rust trigger: Trigger<( OnAdd<Pressed>, OnRemove<Pressed>, OnAdd<InteractionDisabled>, OnRemove<InteractionDisabled>, OnInsert<Hovered>, )>, ``` In scenarios like this, there is a lot of repetition of `On`. These are expected to be very high-traffic APIs especially in UI contexts, so ergonomics and readability are critical. By renaming `Trigger` to `On`, we can make these APIs read more cleanly and get rid of the repetition: ```rust app.add_observer(|trigger: On<Explode>| { info!("Entity {} exploded!", trigger.target()); }); ``` ```rust trigger: On<( Add<Pressed>, Remove<Pressed>, Add<InteractionDisabled>, Remove<InteractionDisabled>, Insert<Hovered>, )>, ``` Names like `On<Add<Pressed>>` emphasize the actual event listener nature more than `Trigger<OnAdd<Pressed>>`, and look cleaner. This *also* frees up the `Trigger` name if we want to use it for the observer event type, splitting them out from buffered events (bikeshedding this is out of scope for this PR though). For prior art: [`bevy_eventlistener`](https://github.com/aevyrie/bevy_eventlistener) used [`On`](https://docs.rs/bevy_eventlistener/latest/bevy_eventlistener/event_listener/struct.On.html) for its event listener type. Though in our case, the observer is the event listener, and `On` is just a type containing information about the triggered event. ## Solution Steal from `bevy_event_listener` by @aevyrie and use `On`. - Rename `Trigger` to `On` - Rename `OnAdd` to `Add` - Rename `OnInsert` to `Insert` - Rename `OnReplace` to `Replace` - Rename `OnRemove` to `Remove` - Rename `OnDespawn` to `Despawn` ## Discussion ### Naming Conflicts?? Using a name like `Add` might initially feel like a very bad idea, since it risks conflict with `core::ops::Add`. However, I don't expect this to be a big problem in practice. - You rarely need to actually implement the `Add` trait, especially in modules that would use the Bevy ECS. - In the rare cases where you *do* get a conflict, it is very easy to fix by just disambiguating, for example using `ops::Add`. - The `Add` event is a struct while the `Add` trait is a trait (duh), so the compiler error should be very obvious. For the record, renaming `OnAdd` to `Add`, I got exactly *zero* errors or conflicts within Bevy itself. But this is of course not entirely representative of actual projects *using* Bevy. You might then wonder, why not use `Added`? This would conflict with the `Added` query filter, so it wouldn't work. Additionally, the current naming convention for observer events does not use past tense. ### Documentation This does make documentation slightly more awkward when referring to `On` or its methods. Previous docs often referred to `Trigger::target` or "sends a `Trigger`" (which is... a bit strange anyway), which would now be `On::target` and "sends an observer `Event`". You can see the diff in this PR to see some of the effects. I think it should be fine though, we may just need to reword more documentation to read better. |
||
![]() |
12f8f9c07c
|
Thoroughly document the current state of observers (#19590)
# Objective The documentation for observers is not very good. This poses a problem to users, but *also* causes serious problems for engine devs, as they attempt to improve assorted issues surrounding observers. This PR: - Fixes #14084. - Fixes #14726. - Fixes #16538. - Closes #18914, by attempting to solve the same issue. To keep this PR at all reviewable, I've opted to simply note the various limitations (some may call them bugs!) in place, rather than attempting to fix them. There is a huge amount of cleanup work to be done here: see https://github.com/orgs/bevyengine/projects/17. ## Solution - Write good module docs for observers, offering bread crumbs to the most common methods and techniques and comparing-and-contrasting as needed. - Fix any actively misleading documentation. - Try to explain how the various bits of the (public?!) internals are related. --------- Co-authored-by: Chris Biscardi <chris@christopherbiscardi.com> Co-authored-by: Joona Aalto <jondolf.dev@gmail.com> |
||
![]() |
6ddd0f16a8
|
Component lifecycle reorganization and documentation (#19543)
# Objective I set out with one simple goal: clearly document the differences between each of the component lifecycle events via module docs. Unfortunately, no such module existed: the various lifecycle code was scattered to the wind. Without a unified module, it's very hard to discover the related types, and there's nowhere good to put my shiny new documentation. ## Solution 1. Unify the assorted types into a single `bevy_ecs::component_lifecycle` module. 2. Write docs. 3. Write a migration guide. ## Testing Thanks CI! ## Follow-up 1. The lifecycle event names are pretty confusing, especially `OnReplace`. We should consider renaming those. No bikeshedding in my PR though! 2. Observers need real module docs too :( 3. Any additional functional changes should be done elsewhere; this is a simple docs and re-org PR. --------- Co-authored-by: theotherphil <phil.j.ellison@gmail.com> |
||
![]() |
064e5e48b4
|
Remove entity placeholder from observers (#19440)
# Objective `Entity::PLACEHOLDER` acts as a magic number that will *probably* never really exist, but it certainly could. And, `Entity` has a niche, so the only reason to use `PLACEHOLDER` is as an alternative to `MaybeUninit` that trades safety risks for logic risks. As a result, bevy has generally advised against using `PLACEHOLDER`, but we still use if for a lot internally. This pr starts removing internal uses of it, starting from observers. ## Solution Change all trigger target related types from `Entity` to `Option<Entity>` Small migration guide to come. ## Testing CI ## Future Work This turned a lot of code from ```rust trigger.target() ``` to ```rust trigger.target().unwrap() ``` The extra panic is no worse than before; it's just earlier than panicking after passing the placeholder to something else. But this is kinda annoying. I would like to add a `TriggerMode` or something to `Event` that would restrict what kinds of targets can be used for that event. Many events like `Removed` etc, are always triggered with a target. We can make those have a way to assume Some, etc. But I wanted to save that for a future pr. |
||
![]() |
54c9f03021
|
Mention in .add_observer() docs that first parameter must be a Trigger (#19315)
# Objective Fix https://github.com/bevyengine/bevy/issues/13860 ## Solution Add note in docs that Trigger must be the first parameter of observer systems |
||
![]() |
0b4858726c
|
Make entity::index non max (#18704)
# Objective There are two problems this aims to solve. First, `Entity::index` is currently a `u32`. That means there are `u32::MAX + 1` possible entities. Not only is that awkward, but it also make `Entity` allocation more difficult. I discovered this while working on remote entity reservation, but even on main, `Entities` doesn't handle the `u32::MAX + 1` entity very well. It can not be batch reserved because that iterator uses exclusive ranges, which has a maximum upper bound of `u32::MAX - 1`. In other words, having `u32::MAX` as a valid index can be thought of as a bug right now. We either need to make that invalid (this PR), which makes Entity allocation cleaner and makes remote reservation easier (because the length only needs to be u32 instead of u64, which, in atomics is a big deal), or we need to take another pass at `Entities` to make it handle the `u32::MAX` index properly. Second, `TableRow`, `ArchetypeRow` and `EntityIndex` (a type alias for u32) all have `u32` as the underlying type. That means using these as the index type in a `SparseSet` uses 64 bits for the sparse list because it stores `Option<IndexType>`. By using `NonMaxU32` here, we cut the memory of that list in half. To my knowledge, `EntityIndex` is the only thing that would really benefit from this niche. `TableRow` and `ArchetypeRow` I think are not stored in an `Option` in bulk. But if they ever are, this would help. Additionally this ensures `TableRow::INVALID` and `ArchetypeRow::INVALID` never conflict with an actual row, which in a nice bonus. As a related note, if we do components as entities where `ComponentId` becomes `Entity`, the the `SparseSet<ComponentId>` will see a similar memory improvement too. ## Solution Create a new type `EntityRow` that wraps `NonMaxU32`, similar to `TableRow` and `ArchetypeRow`. Change `Entity::index` to this type. ## Downsides `NonMax` is implemented as a `NonZero` with a binary inversion. That means accessing and storing the value takes one more instruction. I don't think that's a big deal, but it's worth mentioning. As a consequence, `to_bits` uses `transmute` to skip the inversion which keeps it a nop. But that also means that ordering has now flipped. In other words, higher indices are considered less than lower indices. I don't think that's a problem, but it's also worth mentioning. ## Alternatives We could keep the index as a u32 type and just document that `u32::MAX` is invalid, modifying `Entities` to ensure it never gets handed out. (But that's not enforced by the type system.) We could still take advantage of the niche here in `ComponentSparseSet`. We'd just need some unsafe manual conversions, which is probably fine, but opens up the possibility for correctness problems later. We could change `Entities` to fully support the `u32::MAX` index. (But that makes `Entities` more complex and potentially slightly slower.) ## Testing - CI - A few tests were changed because they depend on different ordering and `to_bits` values. ## Future Work - It might be worth removing the niche on `Entity::generation` since there is now a different niche. - We could move `Entity::generation` into it's own type too for clarity. - We should change `ComponentSparseSet` to take advantage of the new niche. (This PR doesn't change that yet.) - Consider removing or updating `Identifier`. This is only used for `Entity`, so it might be worth combining since `Entity` is now more unique. --------- Co-authored-by: atlv <email@atlasdostal.com> Co-authored-by: Zachary Harrold <zac@harrold.com.au> |
||
![]() |
5ed8e0639a
|
Merge ObserverState and Observer into single component (#18728)
# Objective - bevy removed `Observe` type parameters in #15151 ,it enables merging `Observer` and `ObserverState ` into a single component. with this consolidation ,we can improve efficiency while reducing boilerplate. ## Solution - remove `ObserverState `and merge it into `Observer` ## Testing 40%~60% performance win due to removal of redundant look up.  This also improves ergonomics when using dynamic observer ```rust // previously world.spawn(ObserverState { // SAFETY: we registered `event_a` above and it matches the type of EventA descriptor: unsafe { ObserverDescriptor::default().with_events(vec![event_a]) }, runner: |mut world, _trigger, _ptr, _propagate| { world.resource_mut::<Order>().observed("event_a"); }, ..Default::default() }); // now let observe = unsafe { Observer::with_dynamic_runner(|mut world, _trigger, _ptr, _propagate| { world.resource_mut::<Order>().observed("event_a"); }) .with_event(event_a) }; world.spawn(observe); ``` |
||
![]() |
5f936aefc8
|
Prevent exclusive systems from being used as observers (#19033)
# Objective Prevent using exclusive systems as observers. Allowing them is unsound, because observers are only expected to have `DeferredWorld` access, and the observer infrastructure will keep pointers that are invalidated by the creation of `&mut World`. See https://github.com/bevyengine/bevy/actions/runs/14778342801/job/41491517847?pr=19011 for a MIRI failure in a recent PR caused by an exclusive system being used as an observer in a test. ## Solution Have `Observer::new` panic if `System::is_exclusive()` is true. Document that method, and methods that call it, as panicking. (It should be possible to express this in the type system so that the calls won't even compile, but I did not want to attempt that.) ## Testing Added a unit test that calls `World::add_observer` with an exclusive system. |
||
![]() |
b516e78317
|
Bump crate-ci/typos from 1.31.1 to 1.32.0 (#19072)
Adopted #19066. Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.31.1 to 1.32.0. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
![]() |
e9a0ef49f9
|
Rename bevy_platform_support to bevy_platform (#18813)
# Objective The goal of `bevy_platform_support` is to provide a set of platform agnostic APIs, alongside platform-specific functionality. This is a high traffic crate (providing things like HashMap and Instant). Especially in light of https://github.com/bevyengine/bevy/discussions/18799, it deserves a friendlier / shorter name. Given that it hasn't had a full release yet, getting this change in before Bevy 0.16 makes sense. ## Solution - Rename `bevy_platform_support` to `bevy_platform`. |
||
![]() |
714b4a43d6
|
Change with_related to work with a Bundle and added with_relationships method (#18699)
# Objective Fixes #18678 ## Solution Moved the current `with_related` method to `with_relationships` and added a new `with_related` that uses a bundle. I'm not entirely sold on the name just yet, if anyone has any ideas let me know. ## Testing I wasn't able to test these changes because it crashed my computer every time I tried (fun). But there don't seem to be any tests that use the old `with_related` method so it should be fine, hopefully ## Showcase ```rust commands.spawn_empty() .with_related::<Relationship>(Name::new("Related thingy")) .with_relationships(|rel| { rel.spawn(Name::new("Second related thingy")); }); ``` --------- Co-authored-by: Carter Anderson <mcanders1@gmail.com> |
||
![]() |
f57c7a43c4
|
reexport entity set collections in entity module (#18413)
# Objective Unlike for their helper typers, the import paths for `unique_array::UniqueEntityArray`, `unique_slice::UniqueEntitySlice`, `unique_vec::UniqueEntityVec`, `hash_set::EntityHashSet`, `hash_map::EntityHashMap`, `index_set::EntityIndexSet`, `index_map::EntityIndexMap` are quite redundant. When looking at the structure of `hashbrown`, we can also see that while both `HashSet` and `HashMap` have their own modules, the main types themselves are re-exported to the crate level. ## Solution Re-export the types in their shared `entity` parent module, and simplify the imports where they're used. |
||
![]() |
834260845a
|
Ensure spawning related entities in an OnAdd observer downstream of a World::spawn in a Command does not cause a crash (#18545)
# Objective fixes #18452. ## Solution Spawning used to flush commands only, but those commands can reserve entities. Now, spawning flushes everything, including reserved entities. I checked, and this was the only place where `flush_commands` is used instead of `flush` by mistake. ## Testing I simplified the MRE from #18452 into its own test, which fails on main, but passes on this branch. |
||
![]() |
664000f848
|
Improve derive(Event) and simplify macro code (#18083)
# Objective simplify some code and improve Event macro Closes https://github.com/bevyengine/bevy/issues/14336, # Showcase you can now write derive Events like so ```rust #[derive(event)] #[event(auto_propagate, traversal = MyType)] struct MyEvent; ``` |
||
![]() |
20813aed64
|
Handle TriggerTargets that are combinations for components/entities (#18024)
# Objective * Fixes https://github.com/bevyengine/bevy/issues/14074 * Applies CI fixes for #16326 It is currently not possible to issues a trigger that targets a specific list of components AND a specific list of entities ## Solution We can now use `((A, B), (entity_1, entity_2))` as a trigger target, as well as the reverse ## Testing Added a unit test. The triggering rules for observers are quite confusing: Triggers once per entity target For each entity target, an observer system triggers if any of its components matches the trigger target components (but it triggers at most once, since we use an internal counter to make sure that an observer can run at most once per entity target) (copied from #14563) (copied from #16326) ## Notes All credit to @BenjaminBrienen and @cBournhonesque! Just applying a small fix to this PR so it can be merged. --------- Co-authored-by: Benjamin Brienen <Benjamin.Brienen@outlook.com> Co-authored-by: Christian Hughes <xdotdash@gmail.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
5241e09671
|
Upgrade to Rust Edition 2024 (#17967)
# Objective - Fixes #17960 ## Solution - Followed the [edition upgrade guide](https://doc.rust-lang.org/edition-guide/editions/transitioning-an-existing-project-to-a-new-edition.html) ## Testing - CI --- ## Summary of Changes ### Documentation Indentation When using lists in documentation, proper indentation is now linted for. This means subsequent lines within the same list item must start at the same indentation level as the item. ```rust /* Valid */ /// - Item 1 /// Run-on sentence. /// - Item 2 struct Foo; /* Invalid */ /// - Item 1 /// Run-on sentence. /// - Item 2 struct Foo; ``` ### Implicit `!` to `()` Conversion `!` (the never return type, returned by `panic!`, etc.) no longer implicitly converts to `()`. This is particularly painful for systems with `todo!` or `panic!` statements, as they will no longer be functions returning `()` (or `Result<()>`), making them invalid systems for functions like `add_systems`. The ideal fix would be to accept functions returning `!` (or rather, _not_ returning), but this is blocked on the [stabilisation of the `!` type itself](https://doc.rust-lang.org/std/primitive.never.html), which is not done. The "simple" fix would be to add an explicit `-> ()` to system signatures (e.g., `|| { todo!() }` becomes `|| -> () { todo!() }`). However, this is _also_ banned, as there is an existing lint which (IMO, incorrectly) marks this as an unnecessary annotation. So, the "fix" (read: workaround) is to put these kinds of `|| -> ! { ... }` closuers into variables and give the variable an explicit type (e.g., `fn()`). ```rust // Valid let system: fn() = || todo!("Not implemented yet!"); app.add_systems(..., system); // Invalid app.add_systems(..., || todo!("Not implemented yet!")); ``` ### Temporary Variable Lifetimes The order in which temporary variables are dropped has changed. The simple fix here is _usually_ to just assign temporaries to a named variable before use. ### `gen` is a keyword We can no longer use the name `gen` as it is reserved for a future generator syntax. This involved replacing uses of the name `gen` with `r#gen` (the raw-identifier syntax). ### Formatting has changed Use statements have had the order of imports changed, causing a substantial +/-3,000 diff when applied. For now, I have opted-out of this change by amending `rustfmt.toml` ```toml style_edition = "2021" ``` This preserves the original formatting for now, reducing the size of this PR. It would be a simple followup to update this to 2024 and run `cargo fmt`. ### New `use<>` Opt-Out Syntax Lifetimes are now implicitly included in RPIT types. There was a handful of instances where it needed to be added to satisfy the borrow checker, but there may be more cases where it _should_ be added to avoid breakages in user code. ### `MyUnitStruct { .. }` is an invalid pattern Previously, you could match against unit structs (and unit enum variants) with a `{ .. }` destructuring. This is no longer valid. ### Pretty much every use of `ref` and `mut` are gone Pattern binding has changed to the point where these terms are largely unused now. They still serve a purpose, but it is far more niche now. ### `iter::repeat(...).take(...)` is bad New lint recommends using the more explicit `iter::repeat_n(..., ...)` instead. ## Migration Guide The lifetimes of functions using return-position impl-trait (RPIT) are likely _more_ conservative than they had been previously. If you encounter lifetime issues with such a function, please create an issue to investigate the addition of `+ use<...>`. ## Notes - Check the individual commits for a clearer breakdown for what _actually_ changed. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com> |
||
![]() |
eee7fd5b3e
|
Encapsulate cfg(feature = "track_location") in a type. (#17602)
# Objective Eliminate the need to write `cfg(feature = "track_location")` every time one uses an API that may use location tracking. It's verbose, and a little intimidating. And it requires code outside of `bevy_ecs` that wants to use location tracking needs to either unconditionally enable the feature, or include conditional compilation of its own. It would be good for users to be able to log locations when they are available without needing to add feature flags to their own crates. Reduce the number of cases where code compiles with the `track_location` feature enabled, but not with it disabled, or vice versa. It can be hard to remember to test it both ways! Remove the need to store a `None` in `HookContext` when the `track_location` feature is disabled. ## Solution Create an `MaybeLocation<T>` type that contains a `T` if the `track_location` feature is enabled, and is a ZST if it is not. The overall API is similar to `Option`, but whether the value is `Some` or `None` is set at compile time and is the same for all values. Default `T` to `&'static Location<'static>`, since that is the most common case. Remove all `cfg(feature = "track_location")` blocks outside of the implementation of that type, and instead call methods on it. When `track_location` is disabled, `MaybeLocation` is a ZST and all methods are `#[inline]` and empty, so they should be entirely removed by the compiler. But the code will still be visible to the compiler and checked, so if it compiles with the feature disabled then it should also compile with it enabled, and vice versa. ## Open Questions Where should these types live? I put them in `change_detection` because that's where the existing `MaybeLocation` types were, but we now use these outside of change detection. While I believe that the compiler should be able to remove all of these calls, I have not actually tested anything. If we want to take this approach, what testing is required to ensure it doesn't impact performance? ## Migration Guide Methods like `Ref::changed_by()` that return a `&'static Location<'static>` will now be available even when the `track_location` feature is disabled, but they will return a new `MaybeLocation` type. `MaybeLocation` wraps a `&'static Location<'static>` when the feature is enabled, and is a ZST when the feature is disabled. Existing code that needs a `&Location` can call `into_option().unwrap()` to recover it. Many trait impls are forwarded, so if you only need `Display` then no changes will be necessary. If that code was conditionally compiled, you may instead want to use the methods on `MaybeLocation` to remove the need for conditional compilation. Code that constructs a `Ref`, `Mut`, `Res`, or `ResMut` will now need to provide location information unconditionally. If you are creating them from existing Bevy types, you can obtain a `MaybeLocation` from methods like `Table::get_changed_by_slice_for()` or `ComponentSparseSet::get_with_ticks`. Otherwise, you will need to store a `MaybeLocation` next to your data and use methods like `as_ref()` or `as_mut()` to obtain wrapped references. |
||
![]() |
1b7db895b7
|
Harden proc macro path resolution and add integration tests. (#17330)
This pr uses the `extern crate self as` trick to make proc macros behave the same way inside and outside bevy. # Objective - Removes noise introduced by `crate as` in the whole bevy repo. - Fixes #17004. - Hardens proc macro path resolution. ## TODO - [x] `BevyManifest` needs cleanup. - [x] Cleanup remaining `crate as`. - [x] Add proper integration tests to the ci. ## Notes - `cargo-manifest-proc-macros` is written by me and based/inspired by the old `BevyManifest` implementation and [`bkchr/proc-macro-crate`](https://github.com/bkchr/proc-macro-crate). - What do you think about the new integration test machinery I added to the `ci`? More and better integration tests can be added at a later stage. The goal of these integration tests is to simulate an actual separate crate that uses bevy. Ideally they would lightly touch all bevy crates. ## Testing - Needs RA test - Needs testing from other users - Others need to run at least `cargo run -p ci integration-test` and verify that they work. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
3c8fae2390
|
Improved Entity Mapping and Cloning (#17687)
Fixes #17535 Bevy's approach to handling "entity mapping" during spawning and cloning needs some work. The addition of [Relations](https://github.com/bevyengine/bevy/pull/17398) both [introduced a new "duplicate entities" bug when spawning scenes in the scene system](#17535) and made the weaknesses of the current mapping system exceedingly clear: 1. Entity mapping requires _a ton_ of boilerplate (implement or derive VisitEntities and VisitEntitesMut, then register / reflect MapEntities). Knowing the incantation is challenging and if you forget to do it in part or in whole, spawning subtly breaks. 2. Entity mapping a spawned component in scenes incurs unnecessary overhead: look up ReflectMapEntities, create a _brand new temporary instance_ of the component using FromReflect, map the entities in that instance, and then apply that on top of the actual component using reflection. We can do much better. Additionally, while our new [Entity cloning system](https://github.com/bevyengine/bevy/pull/16132) is already pretty great, it has some areas we can make better: * It doesn't expose semantic info about the clone (ex: ignore or "clone empty"), meaning we can't key off of that in places where it would be useful, such as scene spawning. Rather than duplicating this info across contexts, I think it makes more sense to add that info to the clone system, especially given that we'd like to use cloning code in some of our spawning scenarios. * EntityCloner is currently built in a way that prioritizes a single entity clone * EntityCloner's recursive cloning is built to be done "inside out" in a parallel context (queue commands that each have a clone of EntityCloner). By making EntityCloner the orchestrator of the clone we can remove internal arcs, improve the clarity of the code, make EntityCloner mutable again, and simplify the builder code. * EntityCloner does not currently take into account entity mapping. This is necessary to do true "bullet proof" cloning, would allow us to unify the per-component scene spawning and cloning UX, and ultimately would allow us to use EntityCloner in place of raw reflection for scenes like `Scene(World)` (which would give us a nice performance boost: fewer archetype moves, less reflection overhead). ## Solution ### Improved Entity Mapping First, components now have first-class "entity visiting and mapping" behavior: ```rust #[derive(Component, Reflect)] #[reflect(Component)] struct Inventory { size: usize, #[entities] items: Vec<Entity>, } ``` Any field with the `#[entities]` annotation will be viewable and mappable when cloning and spawning scenes. Compare that to what was required before! ```rust #[derive(Component, Reflect, VisitEntities, VisitEntitiesMut)] #[reflect(Component, MapEntities)] struct Inventory { #[visit_entities(ignore)] size: usize, items: Vec<Entity>, } ``` Additionally, for relationships `#[entities]` is implied, meaning this "just works" in scenes and cloning: ```rust #[derive(Component, Reflect)] #[relationship(relationship_target = Children)] #[reflect(Component)] struct ChildOf(pub Entity); ``` Note that Component _does not_ implement `VisitEntities` directly. Instead, it has `Component::visit_entities` and `Component::visit_entities_mut` methods. This is for a few reasons: 1. We cannot implement `VisitEntities for C: Component` because that would conflict with our impl of VisitEntities for anything that implements `IntoIterator<Item=Entity>`. Preserving that impl is more important from a UX perspective. 2. We should not implement `Component: VisitEntities` VisitEntities in the Component derive, as that would increase the burden of manual Component trait implementors. 3. Making VisitEntitiesMut directly callable for components would make it easy to invalidate invariants defined by a component author. By putting it in the `Component` impl, we can make it harder to call naturally / unavailable to autocomplete using `fn visit_entities_mut(this: &mut Self, ...)`. `ReflectComponent::apply_or_insert` is now `ReflectComponent::apply_or_insert_mapped`. By moving mapping inside this impl, we remove the need to go through the reflection system to do entity mapping, meaning we no longer need to create a clone of the target component, map the entities in that component, and patch those values on top. This will make spawning mapped entities _much_ faster (The default `Component::visit_entities_mut` impl is an inlined empty function, so it will incur no overhead for unmapped entities). ### The Bug Fix To solve #17535, spawning code now skips entities with the new `ComponentCloneBehavior::Ignore` and `ComponentCloneBehavior::RelationshipTarget` variants (note RelationshipTarget is a temporary "workaround" variant that allows scenes to skip these components. This is a temporary workaround that can be removed as these cases should _really_ be using EntityCloner logic, which should be done in a followup PR. When that is done, `ComponentCloneBehavior::RelationshipTarget` can be merged into the normal `ComponentCloneBehavior::Custom`). ### Improved Cloning * `Option<ComponentCloneHandler>` has been replaced by `ComponentCloneBehavior`, which encodes additional intent and context (ex: `Default`, `Ignore`, `Custom`, `RelationshipTarget` (this last one is temporary)). * Global per-world entity cloning configuration has been removed. This felt overly complicated, increased our API surface, and felt too generic. Each clone context can have different requirements (ex: what a user wants in a specific system, what a scene spawner wants, etc). I'd prefer to see how far context-specific EntityCloners get us first. * EntityCloner's internals have been reworked to remove Arcs and make it mutable. * EntityCloner is now directly stored on EntityClonerBuilder, simplifying the code somewhat * EntityCloner's "bundle scratch" pattern has been moved into the new BundleScratch type, improving its usability and making it usable in other contexts (such as future cross-world cloning code). Currently this is still private, but with some higher level safe APIs it could be used externally for making dynamic bundles * EntityCloner's recursive cloning behavior has been "externalized". It is now responsible for orchestrating recursive clones, meaning it no longer needs to be sharable/clone-able across threads / read-only. * EntityCloner now does entity mapping during clones, like scenes do. This gives behavior parity and also makes it more generically useful. * `RelatonshipTarget::RECURSIVE_SPAWN` is now `RelationshipTarget::LINKED_SPAWN`, and this field is used when cloning relationship targets to determine if cloning should happen recursively. The new `LINKED_SPAWN` term was picked to make it more generically applicable across spawning and cloning scenarios. ## Next Steps * I think we should adapt EntityCloner to support cross world cloning. I think this PR helps set the stage for that by making the internals slightly more generalized. We could have a CrossWorldEntityCloner that reuses a lot of this infrastructure. * Once we support cross world cloning, we should use EntityCloner to spawn `Scene(World)` scenes. This would yield significant performance benefits (no archetype moves, less reflection overhead). --------- Co-authored-by: eugineerd <70062110+eugineerd@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
75e8e8c0f6
|
Expose ObserverDescriptor fields (#17623)
# Objective Expose accessor functions to the `ObserverDescriptor`, so that users can use the `Observer` component to inspect what the observer is watching. This would be useful for me, I don't think there's any reason to hide these. |
||
![]() |
9bc0ae33c3
|
Move hashbrown and foldhash out of bevy_utils (#17460)
# Objective - Contributes to #16877 ## Solution - Moved `hashbrown`, `foldhash`, and related types out of `bevy_utils` and into `bevy_platform_support` - Refactored the above to match the layout of these types in `std`. - Updated crates as required. ## Testing - CI --- ## Migration Guide - The following items were moved out of `bevy_utils` and into `bevy_platform_support::hash`: - `FixedState` - `DefaultHasher` - `RandomState` - `FixedHasher` - `Hashed` - `PassHash` - `PassHasher` - `NoOpHash` - The following items were moved out of `bevy_utils` and into `bevy_platform_support::collections`: - `HashMap` - `HashSet` - `bevy_utils::hashbrown` has been removed. Instead, import from `bevy_platform_support::collections` _or_ take a dependency on `hashbrown` directly. - `bevy_utils::Entry` has been removed. Instead, import from `bevy_platform_support::collections::hash_map` or `bevy_platform_support::collections::hash_set` as appropriate. - All of the above equally apply to `bevy::utils` and `bevy::platform_support`. ## Notes - I left `PreHashMap`, `PreHashMapExt`, and `TypeIdMap` in `bevy_utils` as they might be candidates for micro-crating. They can always be moved into `bevy_platform_support` at a later date if desired. |
||
![]() |
f32a6fb205
|
Track callsite for observers & hooks (#15607)
# Objective Fixes #14708 Also fixes some commands not updating tracked location. ## Solution `ObserverTrigger` has a new `caller` field with the `track_change_detection` feature; hooks take an additional caller parameter (which is `Some(…)` or `None` depending on the feature). ## Testing See the new tests in `src/observer/mod.rs` --- ## Showcase Observers now know from where they were triggered (if `track_change_detection` is enabled): ```rust world.observe(move |trigger: Trigger<OnAdd, Foo>| { println!("Added Foo from {}", trigger.caller()); }); ``` ## Migration - hooks now take an additional `Option<&'static Location>` argument --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
ba5e71f53d
|
Parent -> ChildOf (#17427)
Fixes #17412 ## Objective `Parent` uses the "has a X" naming convention. There is increasing sentiment that we should use the "is a X" naming convention for relationships (following #17398). This leaves `Children` as-is because there is prevailing sentiment that `Children` is clearer than `ParentOf` in many cases (especially when treating it like a collection). This renames `Parent` to `ChildOf`. This is just the implementation PR. To discuss the path forward, do so in #17412. ## Migration Guide - The `Parent` component has been renamed to `ChildOf`. |
||
![]() |
5a9bc28502
|
Support non-Vec data structures in relations (#17447)
# Objective
The existing `RelationshipSourceCollection` uses `Vec` as the only
possible backing for our relationships. While a reasonable choice,
benchmarking use cases might reveal that a different data type is better
or faster.
For example:
- Not all relationships require a stable ordering between the
relationship sources (i.e. children). In cases where we a) have many
such relations and b) don't care about the ordering between them, a hash
set is likely a better datastructure than a `Vec`.
- The number of children-like entities may be small on average, and a
`smallvec` may be faster
## Solution
- Implement `RelationshipSourceCollection` for `EntityHashSet`, our
custom entity-optimized `HashSet`.
-~~Implement `DoubleEndedIterator` for `EntityHashSet` to make things
compile.~~
- This implementation was cursed and very surprising.
- Instead, by moving the iterator type on `RelationshipSourceCollection`
from an erased RPTIT to an explicit associated type we can add a trait
bound on the offending methods!
- Implement `RelationshipSourceCollection` for `SmallVec`
## Testing
I've added a pair of new tests to make sure this pattern compiles
successfully in practice!
## Migration Guide
`EntityHashSet` and `EntityHashMap` are no longer re-exported in
`bevy_ecs::entity` directly. If you were not using `bevy_ecs` / `bevy`'s
`prelude`, you can access them through their now-public modules,
`hash_set` and `hash_map` instead.
## Notes to reviewers
The `EntityHashSet::Iter` type needs to be public for this impl to be
allowed. I initially renamed it to something that wasn't ambiguous and
re-exported it, but as @Victoronz pointed out, that was somewhat
unidiomatic.
In
|
||
![]() |
21f1e3045c
|
Relationships (non-fragmenting, one-to-many) (#17398)
This adds support for one-to-many non-fragmenting relationships (with planned paths for fragmenting and non-fragmenting many-to-many relationships). "Non-fragmenting" means that entities with the same relationship type, but different relationship targets, are not forced into separate tables (which would cause "table fragmentation"). Functionally, this fills a similar niche as the current Parent/Children system. The biggest differences are: 1. Relationships have simpler internals and significantly improved performance and UX. Commands and specialized APIs are no longer necessary to keep everything in sync. Just spawn entities with the relationship components you want and everything "just works". 2. Relationships are generalized. Bevy can provide additional built in relationships, and users can define their own. **REQUEST TO REVIEWERS**: _please don't leave top level comments and instead comment on specific lines of code. That way we can take advantage of threaded discussions. Also dont leave comments simply pointing out CI failures as I can read those just fine._ ## Built on top of what we have Relationships are implemented on top of the Bevy ECS features we already have: components, immutability, and hooks. This makes them immediately compatible with all of our existing (and future) APIs for querying, spawning, removing, scenes, reflection, etc. The fewer specialized APIs we need to build, maintain, and teach, the better. ## Why focus on one-to-many non-fragmenting first? 1. This allows us to improve Parent/Children relationships immediately, in a way that is reasonably uncontroversial. Switching our hierarchy to fragmenting relationships would have significant performance implications. ~~Flecs is heavily considering a switch to non-fragmenting relations after careful considerations of the performance tradeoffs.~~ _(Correction from @SanderMertens: Flecs is implementing non-fragmenting storage specialized for asset hierarchies, where asset hierarchies are many instances of small trees that have a well defined structure)_ 2. Adding generalized one-to-many relationships is currently a priority for the [Next Generation Scene / UI effort](https://github.com/bevyengine/bevy/discussions/14437). Specifically, we're interested in building reactions and observers on top. ## The changes This PR does the following: 1. Adds a generic one-to-many Relationship system 3. Ports the existing Parent/Children system to Relationships, which now lives in `bevy_ecs::hierarchy`. The old `bevy_hierarchy` crate has been removed. 4. Adds on_despawn component hooks 5. Relationships can opt-in to "despawn descendants" behavior, meaning that the entire relationship hierarchy is despawned when `entity.despawn()` is called. The built in Parent/Children hierarchies enable this behavior, and `entity.despawn_recursive()` has been removed. 6. `world.spawn` now applies commands after spawning. This ensures that relationship bookkeeping happens immediately and removes the need to manually flush. This is in line with the equivalent behaviors recently added to the other APIs (ex: insert). 7. Removes the ValidParentCheckPlugin (system-driven / poll based) in favor of a `validate_parent_has_component` hook. ## Using Relationships The `Relationship` trait looks like this: ```rust pub trait Relationship: Component + Sized { type RelationshipSources: RelationshipSources<Relationship = Self>; fn get(&self) -> Entity; fn from(entity: Entity) -> Self; } ``` A relationship is a component that: 1. Is a simple wrapper over a "target" Entity. 2. Has a corresponding `RelationshipSources` component, which is a simple wrapper over a collection of entities. Every "target entity" targeted by a "source entity" with a `Relationship` has a `RelationshipSources` component, which contains every "source entity" that targets it. For example, the `Parent` component (as it currently exists in Bevy) is the `Relationship` component and the entity containing the Parent is the "source entity". The entity _inside_ the `Parent(Entity)` component is the "target entity". And that target entity has a `Children` component (which implements `RelationshipSources`). In practice, the Parent/Children relationship looks like this: ```rust #[derive(Relationship)] #[relationship(relationship_sources = Children)] pub struct Parent(pub Entity); #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent)] pub struct Children(Vec<Entity>); ``` The Relationship and RelationshipSources derives automatically implement Component with the relevant configuration (namely, the hooks necessary to keep everything in sync). The most direct way to add relationships is to spawn entities with relationship components: ```rust let a = world.spawn_empty().id(); let b = world.spawn(Parent(a)).id(); assert_eq!(world.entity(a).get::<Children>().unwrap(), &[b]); ``` There are also convenience APIs for spawning more than one entity with the same relationship: ```rust world.spawn_empty().with_related::<Children>(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` The existing `with_children` API is now a simpler wrapper over `with_related`. This makes this change largely non-breaking for existing spawn patterns. ```rust world.spawn_empty().with_children(|s| { s.spawn_empty(); s.spawn_empty(); }) ``` There are also other relationship APIs, such as `add_related` and `despawn_related`. ## Automatic recursive despawn via the new on_despawn hook `RelationshipSources` can opt-in to "despawn descendants" behavior, which will despawn all related entities in the relationship hierarchy: ```rust #[derive(RelationshipSources)] #[relationship_sources(relationship = Parent, despawn_descendants)] pub struct Children(Vec<Entity>); ``` This means that `entity.despawn_recursive()` is no longer required. Instead, just use `entity.despawn()` and the relevant related entities will also be despawned. To despawn an entity _without_ despawning its parent/child descendants, you should remove the `Children` component first, which will also remove the related `Parent` components: ```rust entity .remove::<Children>() .despawn() ``` This builds on the on_despawn hook introduced in this PR, which is fired when an entity is despawned (before other hooks). ## Relationships are the source of truth `Relationship` is the _single_ source of truth component. `RelationshipSources` is merely a reflection of what all the `Relationship` components say. By embracing this, we are able to significantly improve the performance of the system as a whole. We can rely on component lifecycles to protect us against duplicates, rather than needing to scan at runtime to ensure entities don't already exist (which results in quadratic runtime). A single source of truth gives us constant-time inserts. This does mean that we cannot directly spawn populated `Children` components (or directly add or remove entities from those components). I personally think this is a worthwhile tradeoff, both because it makes the performance much better _and_ because it means theres exactly one way to do things (which is a philosophy we try to employ for Bevy APIs). As an aside: treating both sides of the relationship as "equivalent source of truth relations" does enable building simple and flexible many-to-many relationships. But this introduces an _inherent_ need to scan (or hash) to protect against duplicates. [`evergreen_relations`](https://github.com/EvergreenNest/evergreen_relations) has a very nice implementation of the "symmetrical many-to-many" approach. Unfortunately I think the performance issues inherent to that approach make it a poor choice for Bevy's default relationship system. ## Followup Work * Discuss renaming `Parent` to `ChildOf`. I refrained from doing that in this PR to keep the diff reasonable, but I'm personally biased toward this change (and using that naming pattern generally for relationships). * [Improved spawning ergonomics](https://github.com/bevyengine/bevy/discussions/16920) * Consider adding relationship observers/triggers for "relationship targets" whenever a source is added or removed. This would replace the current "hierarchy events" system, which is unused upstream but may have existing users downstream. I think triggers are the better fit for this than a buffered event queue, and would prefer not to add that back. * Fragmenting relations: My current idea hinges on the introduction of "value components" (aka: components whose type _and_ value determines their ComponentId, via something like Hashing / PartialEq). By labeling a Relationship component such as `ChildOf(Entity)` as a "value component", `ChildOf(e1)` and `ChildOf(e2)` would be considered "different components". This makes the transition between fragmenting and non-fragmenting a single flag, and everything else continues to work as expected. * Many-to-many support * Non-fragmenting: We can expand Relationship to be a list of entities instead of a single entity. I have largely already written the code for this. * Fragmenting: With the "value component" impl mentioned above, we get many-to-many support "for free", as it would allow inserting multiple copies of a Relationship component with different target entities. Fixes #3742 (If this PR is merged, I think we should open more targeted followup issues for the work above, with a fresh tracking issue free of the large amount of less-directed historical context) Fixes #17301 Fixes #12235 Fixes #15299 Fixes #15308 ## Migration Guide * Replace `ChildBuilder` with `ChildSpawnerCommands`. * Replace calls to `.set_parent(parent_id)` with `.insert(Parent(parent_id))`. * Replace calls to `.replace_children()` with `.remove::<Children>()` followed by `.add_children()`. Note that you'll need to manually despawn any children that are not carried over. * Replace calls to `.despawn_recursive()` with `.despawn()`. * Replace calls to `.despawn_descendants()` with `.despawn_related::<Children>()`. * If you have any calls to `.despawn()` which depend on the children being preserved, you'll need to remove the `Children` component first. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> |
||
![]() |
237c6b207e
|
Remove Event: Component trait bound using a wrapper type which impls Component (#17380)
# Objective As raised in https://github.com/bevyengine/bevy/pull/17317, the `Event: Component` trait bound is confusing to users. In general, a type `E` (like `AppExit`) which implements `Event` should not: - be stored as a component on an entity - be a valid option for `Query<&AppExit>` - require the storage type and other component metadata to be specified Events are not components (even if they one day use some of the same internal mechanisms), and this trait bound is confusing to users. We're also automatically generating `Component` impls with our derive macro, which should be avoided when possible to improve explicitness and avoid conflicts with user impls. Closes #17317, closes #17333 ## Solution - We only care that each unique event type gets a unique `ComponentId` - dynamic events need their own tools for getting identifiers anyways - This avoids complicating the internals of `ComponentId` generation. - Clearly document why this cludge-y solution exists. In the medium term, I think that either a) properly generalizing `ComponentId` (and moving it into `bevy_reflect?) or b) using a new-typed `Entity` as the key for events is more correct. This change is stupid simple though, and removes the offending trait bound in a way that doesn't introduce complex tech debt and does not risk changes to the internals. This change does not: - restrict our ability to implement dynamic buffered events (the main improvement over #17317) - there's still a fair bit of work to do, but this is a step in the right direction - limit our ability to store event metadata on entities in the future - make it harder for users to work with types that are both events and components (just add the derive / trait bound) ## Migration Guide The `Event` trait no longer requires the `Component` trait. If you were relying on this behavior, change your trait bounds from `Event` to `Event + Component`. If you also want your `Event` type to implement `Component`, add a derive. --------- Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com> |
||
![]() |
dcff8f3ecb
|
Make ObservedBy public (#17297)
# Objective - Currently, the `ObservedBy`-component is only public within the `bevy_ecs` crate. Sometimes it is desirable to refer to this component in the "game-code". Two examples that come in mind: - Clearing all components in an entity, but intending to keep the existing observers: Making `ObservedBy` public allows us to use `commands.entity(entity).retain::<ObservedBy>();`, which clears all other components, but keeps `ObservedBy`, which prevents the Observers from despawning. - The opposite of the above, clearing all of entities' Observers: `commands.entity(entity).remove::<ObservedBy>` will despawn all associated Observers. Admittedly, a cleaner solution would be something like `commands.entity(entity).clear_observers()`, but this is sufficient. ## Solution - Removed `(crate)` "rule" and added `ObservedBy` to the prelude-module ## Testing - Linked `bevy_ecs` locally with another project to see if `ObservedBy` could be referenced. |
||
![]() |
0403948aa2
|
Remove Implicit std Prelude from no_std Crates (#17086)
# Background In `no_std` compatible crates, there is often an `std` feature which will allow access to the standard library. Currently, with the `std` feature _enabled_, the [`std::prelude`](https://doc.rust-lang.org/std/prelude/index.html) is implicitly imported in all modules. With the feature _disabled_, instead the [`core::prelude`](https://doc.rust-lang.org/core/prelude/index.html) is implicitly imported. This creates a subtle and pervasive issue where `alloc` items _may_ be implicitly included (if `std` is enabled), or must be explicitly included (if `std` is not enabled). # Objective - Make the implicit imports for `no_std` crates consistent regardless of what features are/not enabled. ## Solution - Replace the `cfg_attr` "double negative" `no_std` attribute with conditional compilation to _include_ `std` as an external crate. ```rust // Before #![cfg_attr(not(feature = "std"), no_std)] // After #![no_std] #[cfg(feature = "std")] extern crate std; ``` - Fix imports that are currently broken but are only now visible with the above fix. ## Testing - CI ## Notes I had previously used the "double negative" version of `no_std` based on general consensus that it was "cleaner" within the Rust embedded community. However, this implicit prelude issue likely was considered when forming this consensus. I believe the reason why is the items most affected by this issue are provided by the `alloc` crate, which is rarely used within embedded but extensively used within Bevy. |
||
![]() |
0f2b2de333
|
Move some structs that impl Command to methods on World and EntityWorldMut (#16999)
## Objective Commands were previously limited to structs that implemented `Command`. Now there are blanket implementations for closures, which (in my opinion) are generally preferable. Internal commands within `commands/mod.rs` have been switched from structs to closures, but there are a number of internal commands in other areas of the engine that still use structs. I'd like to tidy these up by moving their implementations to methods on `World`/`EntityWorldMut` and changing `Commands` to use those methods through closures. This PR handles the following: - `TriggerEvent` and `EmitDynamicTrigger` double as commands and helper structs, and can just be moved to `World` methods. - Four structs that enabled insertion/removal of components via reflection. This functionality shouldn't be exclusive to commands, and can be added to `EntityWorldMut`. - Five structs that mostly just wrapped `World` methods, and can be replaced with closures that do the same thing. ## Solution - __Observer Triggers__ (`observer/trigger_event.rs` and `observer/mod.rs`) - Moved the internals of `TriggerEvent` to the `World` methods that used it. - Replaced `EmitDynamicTrigger` with two `World` methods: - `trigger_targets_dynamic` - `trigger_targets_dynamic_ref` - `TriggerTargets` was now the only thing in `observer/trigger_event.rs`, so it's been moved to `observer/mod.rs` and `trigger_event.rs` was deleted. - __Reflection Insert/Remove__ (`reflect/entity_commands.rs`) - Replaced the following `Command` impls with equivalent methods on `EntityWorldMut`: - `InsertReflect` -> `insert_reflect` - `InsertReflectWithRegistry` -> `insert_reflect_with_registry` - `RemoveReflect` -> `remove_reflect` - `RemoveReflectWithRegistry` -> `remove_reflect_with_registry` - __System Registration__ (`system/system_registry.rs`) - The following `Command` impls just wrapped a `World` method and have been replaced with closures: - `RunSystemWith` - `UnregisterSystem` - `RunSystemCachedWith` - `UnregisterSystemCached` - `RegisterSystem` called a helper function that basically worked as a constructor for `RegisteredSystem` and made sure it came with a marker component. That helper function has been replaced with `RegisteredSystem::new` and a `#[require]`. ## Possible Addition The extension trait that adds the reflection commands, `ReflectCommandExt`, isn't strictly necessary; we could just `impl EntityCommands`. We could even move them to the same files as the main impls and put it behind a `#[cfg]`. The PR that added it [had a similar conversation](https://github.com/bevyengine/bevy/pull/8895#discussion_r1234713671) and decided to stick with the trait, but we could revisit it here if so desired. |
||
![]() |
1f2d0e6308
|
Add no_std support to bevy_ecs (#16758)
# Objective - Contributes to #15460 ## Solution - Added the following features: - `std` (default) - `async_executor` (default) - `edge_executor` - `critical-section` - `portable-atomic` - Gated `tracing` in `bevy_utils` to allow compilation on certain platforms - Switched from `tracing` to `log` for simple message logging within `bevy_ecs`. Note that `tracing` supports capturing from `log` so this should be an uncontroversial change. - Fixed imports and added feature gates as required - Made `bevy_tasks` optional within `bevy_ecs`. Turns out it's only needed for parallel operations which are already gated behind `multi_threaded` anyway. ## Testing - Added to `compile-check-no-std` CI command - `cargo check -p bevy_ecs --no-default-features --features edge_executor,critical-section,portable-atomic --target thumbv6m-none-eabi` - `cargo check -p bevy_ecs --no-default-features --features edge_executor,critical-section` - `cargo check -p bevy_ecs --no-default-features` ## Draft Release Notes Bevy's core ECS now supports `no_std` platforms. In prior versions of Bevy, it was not possible to work with embedded or niche platforms due to our reliance on the standard library, `std`. This has blocked a number of novel use-cases for Bevy, such as an embedded database for IoT devices, or for creating games on retro consoles. With this release, `bevy_ecs` no longer requires `std`. To use Bevy on a `no_std` platform, you must disable default features and enable the new `edge_executor` and `critical-section` features. You may also need to enable `portable-atomic` and `critical-section` if your platform does not natively support all atomic types and operations used by Bevy. ```toml [dependencies] bevy_ecs = { version = "0.16", default-features = false, features = [ # Required for platforms with incomplete atomics (e.g., Raspberry Pi Pico) "portable-atomic", "critical-section", # Optional "bevy_reflect", "serialize", "bevy_debug_stepping", "edge_executor" ] } ``` Currently, this has been tested on bare-metal x86 and the Raspberry Pi Pico. If you have trouble using `bevy_ecs` on a particular platform, please reach out either through a GitHub issue or in the `no_std` working group on the Bevy Discord server. Keep an eye out for future `no_std` updates as we continue to improve the parity between `std` and `no_std`. We look forward to seeing what kinds of applications are now possible with Bevy! ## Notes - Creating PR in draft to ensure CI is passing before requesting reviews. - This implementation has no support for multithreading in `no_std`, especially due to `NonSend` being unsound if allowed in multithreading. The reason is we cannot check the `ThreadId` in `no_std`, so we have no mechanism to at-runtime determine if access is sound. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Vic <59878206+Victoronz@users.noreply.github.com> |
||
![]() |
61b98ec80f
|
Rename trigger.entity() to trigger.target() (#16716)
# Objective - A `Trigger` has multiple associated `Entity`s - the entity observing the event, and the entity that was targeted by the event. - The field `entity: Entity` encodes no semantic information about what the entity is used for, you can already tell that it's an `Entity` by the type signature! ## Solution - Rename `trigger.entity()` to `trigger.target()` --- ## Changelog - `Trigger`s are associated with multiple entities. `Trigger::entity()` has been renamed to `Trigger::target()` to reflect the semantics of the entity being returned. ## Migration Guide - Rename `Trigger::entity()` to `Trigger::target()`. - Rename `ObserverTrigger::entity` to `ObserverTrigger::target` |
||
![]() |
09b0b5df91
|
Window picking (#16103)
# Objective On the web, it's common to attach observers to windows. As @viridia has discovered, this can be quite a nice paradigm in bevy as well when applied to observers. The changes here are intended to make this possible. + Adds a new default picking back-end as part to the core picking plugin (which can be disabled) that causes pointers on windows to treat the window entity as the final hit, behind everything else. This means clicking empty space now dispatches normal picking events to the window, and is especially nice for drag-and-drop functionality. + Adds a new traversal type, specific to picking events, that causes them to bubble up to the window entity after they reach the root of the hierarchy. ## Solution The window picking back-end is extremely simple, but the bubbling changes are much more complex, since they require doing a different traversal depending on the picking event. To achieve this, `Traversal` has been made generic over an associated sized data type `D`. Observer bounds have been changed such that `Event::Traversal<D>` is required for `Trigger<D>`. A blanket implementation has been added for `()` and `Parent` that preserves the existing functionality. A new `PointerTraversal` traversal has been implemented, with a blanket implementation for `Traversal<Pointer<E>>`. It is still possible to use `Parent` as the traversal for any event, because of the blanket implementation. It is now possible for users to add other custom traversals, which read event data during traversal. ## Testing I tested these changes locally on some picking UI prototypes I have been playing with. I also tested them on the picking examples. --------- Co-authored-by: Martín Maita <47983254+mnmaita@users.noreply.github.com> |
||
![]() |
a35811d088
|
Add Immutable Component Support (#16372)
# Objective - Fixes #16208 ## Solution - Added an associated type to `Component`, `Mutability`, which flags whether a component is mutable, or immutable. If `Mutability= Mutable`, the component is mutable. If `Mutability= Immutable`, the component is immutable. - Updated `derive_component` to default to mutable unless an `#[component(immutable)]` attribute is added. - Updated `ReflectComponent` to check if a component is mutable and, if not, panic when attempting to mutate. ## Testing - CI - `immutable_components` example. --- ## Showcase Users can now mark a component as `#[component(immutable)]` to prevent safe mutation of a component while it is attached to an entity: ```rust #[derive(Component)] #[component(immutable)] struct Foo { // ... } ``` This prevents creating an exclusive reference to the component while it is attached to an entity. This is particularly powerful when combined with component hooks, as you can now fully track a component's value, ensuring whatever invariants you desire are upheld. Before this would be done my making a component private, and manually creating a `QueryData` implementation which only permitted read access. <details> <summary>Using immutable components as an index</summary> ```rust /// This is an example of a component like [`Name`](bevy::prelude::Name), but immutable. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Component)] #[component( immutable, on_insert = on_insert_name, on_replace = on_replace_name, )] pub struct Name(pub &'static str); /// This index allows for O(1) lookups of an [`Entity`] by its [`Name`]. #[derive(Resource, Default)] struct NameIndex { name_to_entity: HashMap<Name, Entity>, } impl NameIndex { fn get_entity(&self, name: &'static str) -> Option<Entity> { self.name_to_entity.get(&Name(name)).copied() } } fn on_insert_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) { let Some(&name) = world.entity(entity).get::<Name>() else { unreachable!() }; let Some(mut index) = world.get_resource_mut::<NameIndex>() else { return; }; index.name_to_entity.insert(name, entity); } fn on_replace_name(mut world: DeferredWorld<'_>, entity: Entity, _component: ComponentId) { let Some(&name) = world.entity(entity).get::<Name>() else { unreachable!() }; let Some(mut index) = world.get_resource_mut::<NameIndex>() else { return; }; index.name_to_entity.remove(&name); } // Setup our name index world.init_resource::<NameIndex>(); // Spawn some entities! let alyssa = world.spawn(Name("Alyssa")).id(); let javier = world.spawn(Name("Javier")).id(); // Check our index let index = world.resource::<NameIndex>(); assert_eq!(index.get_entity("Alyssa"), Some(alyssa)); assert_eq!(index.get_entity("Javier"), Some(javier)); // Changing the name of an entity is also fully capture by our index world.entity_mut(javier).insert(Name("Steven")); // Javier changed their name to Steven let steven = javier; // Check our index let index = world.resource::<NameIndex>(); assert_eq!(index.get_entity("Javier"), None); assert_eq!(index.get_entity("Steven"), Some(steven)); ``` </details> Additionally, users can use `Component<Mutability = ...>` in trait bounds to enforce that a component _is_ mutable or _is_ immutable. When using `Component` as a trait bound without specifying `Mutability`, any component is applicable. However, methods which only work on mutable or immutable components are unavailable, since the compiler must be pessimistic about the type. ## Migration Guide - When implementing `Component` manually, you must now provide a type for `Mutability`. The type `Mutable` provides equivalent behaviour to earlier versions of `Component`: ```rust impl Component for Foo { type Mutability = Mutable; // ... } ``` - When working with generic components, you may need to specify that your generic parameter implements `Component<Mutability = Mutable>` rather than `Component` if you require mutable access to said component. - The entity entry API has had to have some changes made to minimise friction when working with immutable components. Methods which previously returned a `Mut<T>` will now typically return an `OccupiedEntry<T>` instead, requiring you to add an `into_mut()` to get the `Mut<T>` item again. ## Draft Release Notes Components can now be made immutable while stored within the ECS. Components are the fundamental unit of data within an ECS, and Bevy provides a number of ways to work with them that align with Rust's rules around ownership and borrowing. One part of this is hooks, which allow for defining custom behavior at key points in a component's lifecycle, such as addition and removal. However, there is currently no way to respond to _mutation_ of a component using hooks. The reasons for this are quite technical, but to summarize, their addition poses a significant challenge to Bevy's core promises around performance. Without mutation hooks, it's relatively trivial to modify a component in such a way that breaks invariants it intends to uphold. For example, you can use `core::mem::swap` to swap the components of two entities, bypassing the insertion and removal hooks. This means the only way to react to this modification is via change detection in a system, which then begs the question of what happens _between_ that alteration and the next run of that system? Alternatively, you could make your component private to prevent mutation, but now you need to provide commands and a custom `QueryData` implementation to allow users to interact with your component at all. Immutable components solve this problem by preventing the creation of an exclusive reference to the component entirely. Without an exclusive reference, the only way to modify an immutable component is via removal or replacement, which is fully captured by component hooks. To make a component immutable, simply add `#[component(immutable)]`: ```rust #[derive(Component)] #[component(immutable)] struct Foo { // ... } ``` When implementing `Component` manually, there is an associated type `Mutability` which controls this behavior: ```rust impl Component for Foo { type Mutability = Mutable; // ... } ``` Note that this means when working with generic components, you may need to specify that a component is mutable to gain access to certain methods: ```rust // Before fn bar<C: Component>() { // ... } // After fn bar<C: Component<Mutability = Mutable>>() { // ... } ``` With this new tool, creating index components, or caching data on an entity should be more user friendly, allowing libraries to provide APIs relying on components and hooks to uphold their invariants. ## Notes - ~~I've done my best to implement this feature, but I'm not happy with how reflection has turned out. If any reflection SMEs know a way to improve this situation I'd greatly appreciate it.~~ There is an outstanding issue around the fallibility of mutable methods on `ReflectComponent`, but the DX is largely unchanged from `main` now. - I've attempted to prevent all safe mutable access to a component that does not implement `Component<Mutability = Mutable>`, but there may still be some methods I have missed. Please indicate so and I will address them, as they are bugs. - Unsafe is an escape hatch I am _not_ attempting to prevent. Whatever you do with unsafe is between you and your compiler. - I am marking this PR as ready, but I suspect it will undergo fairly major revisions based on SME feedback. - I've marked this PR as _Uncontroversial_ based on the feature, not the implementation. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Benjamin Brienen <benjamin.brienen@outlook.com> Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Co-authored-by: Nuutti Kotivuori <naked@iki.fi> |
||
![]() |
2e267bba5a
|
Entity cloning (#16132)
## Objective Fixes #1515 This PR implements a flexible entity cloning system. The primary use case for it is to clone dynamically-generated entities. Example: ```rs #[derive(Component, Clone)] pub struct Projectile; #[derive(Component, Clone)] pub struct Damage { value: f32, } fn player_input( mut commands: Commands, projectiles: Query<Entity, With<Projectile>>, input: Res<ButtonInput<KeyCode>>, ) { // Fire a projectile if input.just_pressed(KeyCode::KeyF) { commands.spawn((Projectile, Damage { value: 10.0 })); } // Triplicate all active projectiles if input.just_pressed(KeyCode::KeyT) { for projectile in projectiles.iter() { // To triplicate a projectile we need to create 2 more clones for _ in 0..2{ commands.clone_entity(projectile) } } } } ``` ## Solution ### Commands Add a `clone_entity` command to create a clone of an entity with all components that can be cloned. Components that can't be cloned will be ignored. ```rs commands.clone_entity(entity) ``` If there is a need to configure the cloning process (like set to clone recursively), there is a second command: ```rs commands.clone_entity_with(entity, |builder| { builder.recursive(true) }); ``` Both of these commands return `EntityCommands` of the cloned entity, so the copy can be modified afterwards. ### Builder All these commands use `EntityCloneBuilder` internally. If there is a need to clone an entity using `World` instead, it is also possible: ```rs let entity = world.spawn(Component).id(); let entity_clone = world.spawn_empty().id(); EntityCloneBuilder::new(&mut world).clone_entity(entity, entity_clone); ``` Builder has methods to `allow` or `deny` certain components during cloning if required and can be extended by implementing traits on it. This PR includes two `EntityCloneBuilder` extensions: `CloneEntityWithObserversExt` to configure adding cloned entity to observers of the original entity, and `CloneEntityRecursiveExt` to configure cloning an entity recursively. ### Clone implementations By default, all components that implement either `Clone` or `Reflect` will be cloned (with `Clone`-based implementation preferred in case component implements both). This can be overriden on a per-component basis: ```rs impl Component for SomeComponent { const STORAGE_TYPE: StorageType = StorageType::Table; fn get_component_clone_handler() -> ComponentCloneHandler { // Don't clone this component ComponentCloneHandler::Ignore } } ``` ### `ComponentCloneHandlers` Clone implementation specified in `get_component_clone_handler` will get registered in `ComponentCloneHandlers` (stored in `bevy_ecs::component::Components`) at component registration time. The clone handler implementation provided by a component can be overriden after registration like so: ```rs let component_id = world.components().component_id::<Component>().unwrap() world.get_component_clone_handlers_mut() .set_component_handler(component_id, ComponentCloneHandler::Custom(component_clone_custom)) ``` The default clone handler for all components that do not explicitly define one (or don't derive `Component`) is `component_clone_via_reflect` if `bevy_reflect` feature is enabled, and `component_clone_ignore` (noop) otherwise. Default handler can be overriden using `ComponentCloneHandlers::set_default_handler` ### Handlers Component clone handlers can be used to modify component cloning behavior. The general signature for a handler that can be used in `ComponentCloneHandler::Custom` is as follows: ```rs pub fn component_clone_custom( world: &mut DeferredWorld, entity_cloner: &EntityCloner, ) { // implementation } ``` The `EntityCloner` implementation (used internally by `EntityCloneBuilder`) assumes that after calling this custom handler, the `target` entity has the desired version of the component from the `source` entity. ### Builder handler overrides Besides component-defined and world-overriden handlers, `EntityCloneBuilder` also has a way to override handlers locally. It is mainly used to allow configuration methods like `recursive` and `add_observers`. ```rs // From observer clone handler implementation impl CloneEntityWithObserversExt for EntityCloneBuilder<'_> { fn add_observers(&mut self, add_observers: bool) -> &mut Self { if add_observers { self.override_component_clone_handler::<ObservedBy>(ComponentCloneHandler::Custom( component_clone_observed_by, )) } else { self.remove_component_clone_handler_override::<ObservedBy>() } } } ``` ## Testing Includes some basic functionality tests and doctests. Performance-wise this feature is the same as calling `clone` followed by `insert` for every entity component. There is also some inherent overhead due to every component clone handler having to access component data through `World`, but this can be reduced without breaking current public API in a later PR. |
||
![]() |
345f935b1a
|
Add Trigger::components , which lists the component targets that were triggered (#15811)
# Objective - Closes #14774 ## Solution Added: ```rust impl<'w, E, B: Bundle> Trigger<'w, E, B> { pub fn components(&self) -> &[ComponentId]; } ``` I went with storing it in the trigger as a `SmallVec<[Component; 1]>` because a singular target component will be the most common case, and it remains the same size as `Vec<ComponentId>`. ## Testing Added a test. |
||
![]() |
219b5930f1
|
Rename App/World::observe to add_observer , EntityWorldMut::observe_entity to observe . (#15754)
# Objective - Closes #15752 Calling the functions `App::observe` and `World::observe` doesn't make sense because you're not "observing" the `App` or `World`, you're adding an observer that listens for an event that occurs *within* the `World`. We should rename them to better fit this. ## Solution Renames: - `App::observe` -> `App::add_observer` - `World::observe` -> `World::add_observer` - `Commands::observe` -> `Commands::add_observer` - `EntityWorldMut::observe_entity` -> `EntityWorldMut::observe` (Note this isn't a breaking change as the original rename was introduced earlier this cycle.) ## Testing Reusing current tests. |
||
![]() |
336c23c1aa
|
Rename observe to observe_entity on EntityWorldMut (#15616)
# Objective The current observers have some unfortunate footguns where you can end up confused about what is actually being observed. For apps you can chain observe like `app.observe(..).observe(..)` which works like you would expect, but if you try the same with world the first `observe()` will return the `EntityWorldMut` for the created observer, and the second `observe()` will only observe on the observer entity. It took several hours for multiple people on discord to figure this out, which is not a great experience. ## Solution Rename `observe` on entities to `observe_entity`. It's slightly more verbose when you know you have an entity, but it feels right to me that observers for specific things have more specific naming, and it prevents this issue completely. Another possible solution would be to unify `observe` on `App` and `World` to have the same kind of return type, but I'm not sure exactly what that would look like. ## Testing Simple name change, so only concern is docs really. --- ## Migration Guide The `observe()` method on entities has been renamed to `observe_entity()` to prevent confusion about what is being observed in some cases. |
||
![]() |
8fb55dbf59
|
Implement SystemParam::queue() method for blanket implementation of ParamSet (#15599)
# Objective The `queue()` method is an optional trait method which is necessary for deferred operations (such as command queues) to work properly in the context of an observer. This method was omitted from the proc_macro blanket implementation of `ParamSet` for tuples; as a result, SystemParams with deferred application (such as Commands) would not work in observers if they were part of a ParamSet. This appears to have been a simple omission, as `queue()` was already implemented for the separate blanket implementation of `ParamSet` for `Vec<T>`. In both cases, it is a simple pass-through to the component SystemParams. ## Solution Add the `queue()` method implementation to the `impl_param_set` proco macro. ## Testing Added a unit test which clearly demonstrates the issue. It fails before the fix, and passes afterwards. --- |
||
![]() |
5289e18e0b
|
System param validation for observers, system registry and run once (#15526)
# Objective Fixes #15394 ## Solution Observers now validate params. System registry has a new error variant for when system running fails due to invalid parameters. Run once now returns a `Result<Out, RunOnceError>` instead of `Out`. This is more inline with system registry, which also returns a result. I'll address warning messages in #15500. ## Testing Added one test for each case. --- ## Migration Guide - `RunSystemOnce::run_system_once` and `RunSystemOnce::run_system_once_with` now return a `Result<Out>` instead of just `Out` --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Zachary Harrold <zac@harrold.com.au> |
||
![]() |
d70595b667
|
Add core and alloc over std Lints (#15281)
# Objective - Fixes #6370 - Closes #6581 ## Solution - Added the following lints to the workspace: - `std_instead_of_core` - `std_instead_of_alloc` - `alloc_instead_of_core` - Used `cargo +nightly fmt` with [item level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Item%5C%3A) to split all `use` statements into single items. - Used `cargo clippy --workspace --all-targets --all-features --fix --allow-dirty` to _attempt_ to resolve the new linting issues, and intervened where the lint was unable to resolve the issue automatically (usually due to needing an `extern crate alloc;` statement in a crate root). - Manually removed certain uses of `std` where negative feature gating prevented `--all-features` from finding the offending uses. - Used `cargo +nightly fmt` with [crate level use formatting](https://rust-lang.github.io/rustfmt/?version=v1.6.0&search=#Crate%5C%3A) to re-merge all `use` statements matching Bevy's previous styling. - Manually fixed cases where the `fmt` tool could not re-merge `use` statements due to conditional compilation attributes. ## Testing - Ran CI locally ## Migration Guide The MSRV is now 1.81. Please update to this version or higher. ## Notes - This is a _massive_ change to try and push through, which is why I've outlined the semi-automatic steps I used to create this PR, in case this fails and someone else tries again in the future. - Making this change has no impact on user code, but does mean Bevy contributors will be warned to use `core` and `alloc` instead of `std` where possible. - This lint is a critical first step towards investigating `no_std` options for Bevy. --------- Co-authored-by: François Mockers <francois.mockers@vleue.com> |
||
![]() |
35d10866b8
|
Rename init_component & friends (#15454)
# Objective - Fixes #15451 ## Migration Guide - `World::init_component` has been renamed to `register_component`. - `World::init_component_with_descriptor` has been renamed to `register_component_with_descriptor`. - `World::init_bundle` has been renamed to `register_bundle`. - `Components::init_component` has been renamed to `register_component`. - `Components::init_component_with_descriptor` has been renamed to `register_component_with_descriptor`. - `Components::init_resource` has been renamed to `register_resource`. - `Components::init_non_send` had been renamed to `register_non_send`. |
||
![]() |
efda7f3f9c
|
Simpler lint fixes: makes ci lints work but disables a lint for now (#15376)
Takes the first two commits from #15375 and adds suggestions from this comment: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366968300 See #15375 for more reasoning/motivation. ## Rebasing (rerunning) ```rust git switch simpler-lint-fixes git reset --hard main cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "rustfmt" cargo clippy --workspace --all-targets --all-features --fix cargo fmt --all -- --unstable-features --config normalize_comments=true,imports_granularity=Crate cargo fmt --all git add --update git commit --message "clippy" git cherry-pick e6c0b94f6795222310fb812fa5c4512661fc7887 ``` |