-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Disabled marker #12928
Add Disabled marker #12928
Conversation
Adding this to "Needs release notes," I thinks its noteworthy. |
Definitely worth mentioning in release notes, if people aren't aware of the existance of disabled entities it could definitely cause subtle bugs. It could probably even be considered a breaking change 🤔 |
I like how relatively simple the implementation is. I know that flecs uses a bit in the |
crates/bevy_ecs/src/query/mod.rs
Outdated
@@ -21,6 +21,12 @@ pub use par_iter::*; | |||
pub use state::*; | |||
pub use world_query::*; | |||
|
|||
use crate as bevy_ecs; | |||
|
|||
/// A marker component that excludes an entity from queries that don't specifically request them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These docs need to be extended.
I would start with a doc test showing off how this differs from an ordinary component.
crates/bevy_ecs/src/query/mod.rs
Outdated
@@ -21,6 +21,12 @@ pub use par_iter::*; | |||
pub use state::*; | |||
pub use world_query::*; | |||
|
|||
use crate as bevy_ecs; | |||
|
|||
/// A marker component that excludes an entity from queries that don't specifically request them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// A marker component that excludes an entity from queries that don't specifically request them. | |
/// A special marker component that excludes an entity from queries that don't specifically request them. |
flecs uses a bit in the identifier to mark toggle-able versions of components, not for whether or not an individual entity is disabled. There wouldn't be any way to communicate that to existing ids. |
@@ -1136,6 +1136,8 @@ impl<'w> EntityWorldMut<'w> { | |||
/// | |||
/// See [`EntityCommands::retain`](crate::system::EntityCommands::retain) for more details. | |||
pub fn retain<T: Bundle>(&mut self) -> &mut Self { | |||
let disabled_id = self.world.init_component::<Disabled>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This behavior needs to be documented. It's a nice bit of design though!
crates/bevy_ecs/src/query/mod.rs
Outdated
use crate as bevy_ecs; | ||
|
||
/// A marker component that excludes an entity from queries that don't specifically request them. | ||
#[derive(bevy_ecs_macros::Component)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This type needs to be Reflect
, Clone
and Copy
, and the type should be registered in bevy_core
. This is needed for various serialization use cases, namely networking and save games.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The component should probably also use sparse storage to avoid table moves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. It would slow down the iteration in turn though. I think we stick to the simplest design for now until we can benchmark it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't slow down iteration unless you are accessing the component. Since it's a ZST that will likely be never.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh actually you're correct since most queries now need to filter on this, disregard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally quite pleased with the design. I personally think that starting with a marker component approach is substantially similar: we shouldn't move off of it until we have solid benchmarks.
I like that the .enable
and .disable
APIs exist: nice and clear, and signals to new users that this fancy special API exists! It also makes future migrations easier.
Needs more docs before we can merge this though: in particular, both doc tests for Disabled
and a top-level ECS example are essential for helping teach about this important new feature.
I think an example is probably best as a follow-up PR, to avoid example bikeshedding and any blockage from existing systems not responding to Disabled nicely. If systems respond only to changed values, disabling the entity could simply make it appear unchanged. I think rendering has a few of such systems, and ideally the example would be somewhat visual. |
|
||
/// A special marker component to disable an entity. | ||
/// | ||
/// Disabled entities do not show up in most queries, unless the query mentions Disabled. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Disabled entities do not show up in most queries, unless the query mentions Disabled. | |
/// Disabled entities do not show up in queries unless the query mentions [`Disabled`] in some way. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy with this now. We should probably still mention that retain
doesn't strip out the Disabled
component, (or remove that behavior), but that's a simple follow-up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder how existing systems behave when only some entities are disabled (not the whole subtree rooted in them). There may be some that expect some entities to exist but not to be invisible to them. Might be worth adding tests to them for those cases.
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] | ||
pub struct Disabled; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should reflect Component
, and maybe also implement and reflect Serialize
and Deserialize
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes to both.
I'm failing to understand why this would at all be the sane or expected behavior. There is no precedent for it. This is a data model level operation meaning it should have zero side effects on anything beyond the ECS. That is exactly how it behaves in all of the frameworks mentioned. Expecting otherwise would be like the equivalent of expecting
These two things need to be answered for this hypothetical and any other hypothetical. Otherwise we are doing a very poor job of scrutinizing because we're not asking if the things we're hallucinating are relevant. |
Because words have meaning, and "disabled" doesn't mean "hidden". A disabled window is one I can't interact with. |
"Hidden" would have the same problem then because it'd be synonymous with "minimized". Whatever name is chosen is specific to the context of the ECS. Bringing it out of that and expecting it to have applicable meanings to every kind of entity is taking it out of context. The same way "Disabled"/"Hidden" would mean for the ECS & queries because this is an ECS feature. |
@JoJoJet
That's not something that happens in Flecs :) I also prefer explicit > implicit.
It's a feature that's very similar to what other game engines like Unity provide, reinterpreted for ECS. Whether or not you solve it with a marker component, the ability to disable an entity is useful. Consider a laser turret with a beam entity. I want to prevent the laser beam from rendering if the turret is not active. I can't modify the query of the renderer to filter on an additional tag. That's where an engine/query-wide disable feature comes in handy. It's a way to let other systems know to ignore the entity. The semantics of |
I must have misunderstood, I was referring to this comment from Joy https://discord.com/channels/691052431525675048/749335865876021248/1198882444254920704 |
This is a comparison between a world with manual |
That comment is incorrect. It's the other way around, systems are only added to a schedule once they've started matching entities for the first time. This prevents adding 20 systems from a plugin to the scheduler, if the app actually only uses 2. This is not implemented with the |
Would be pretty hard to get any conclusive numbers on it, since it would heavily depends on the number of queries and archetypes. Filtering archetypes is a overal pretty cheap however, think of it like adding |
Ah, my bad. For some reason, I thought flecs queries (and so systems) were excluded in some way any time their matched tables (archetypes) were all empty. I also assumed it was done using the |
This is exactly the kind of added complexity I'd like to avoid. "Disabling" is too vague of a term to be generally meaningful, and it will require workarounds like this to make it behave the way people would expect in the general case. This kind of behavior would be better served by specifically meaningful operations |
@JoJoJet How would you address the laser turret scenario? Delete/recreate the laser beam entity? |
That specific scenario? I would set it to |
Besides some edge cases (like Window entities), in most cases the workarounds would be necessary to have the desired behavior. If I spawn a player and disable it, I'd expect it to be functionally identical to despawning it, except I retain the ability to write systems that re-enable it later. The only way to meaningfully split it up would involve specifying the purpose of each and every query, that way you could have more specific markers like "Unsimulated", but that would be more work than just supporting the more silly cases like Window entities. And there's also the very simple option of adding some marker (or having archetype invariants) to prevent you from disabling entities like Window entities. |
But now the sensor collider still reports collision events and it keeps damaging players. |
You could add a specific marker component that prevents the entity from interacting with the rest of the game -- or even better, you could remove a marker component that allows it to interact with the game. I don't think it's necessarily productive to focus on this one example; my point is that this type of functionality would be better served with operations tailor-made for each purpose |
@JoJoJet Well, hang on now. When you brought up your example of a window being disabled you didn't say it wasn't productive to focus on one example, but now that a counter-example is introduced that would indeed be simpler to implement under the proposed PR, you do say it's not productive to focus on one example. Both yours and Sander's are equally valid scenarios and worthy of proper consideration. There's no need to be dismissive. Personally, as a veteran developer who recently staring playing around with Bevy, this is going to be a very common feature request from people new to Bevy, if it isn't already. I don't see how asking developers to introduce dozens of different marker components in their projects is going to be any less complicated than one generalized marker that can invoke additional logic via observers. In your example of a window being disabled, querying for the presence or absence of a specific marker to minimize or destroy a window is no less complicated than having an observer on a generalized marker that minimizes or destroys said window. It's just two different ways of approaching the problem. Perhaps it would be best to wait until observers land before looking at this again... |
Tailor-made operations can be useful but that's an entirely separate usecase. If a player gets hit it might make sense to disable their hitbox until their invulnerability ends. But a game isn't just one hitbox, you have lots of separate systems, which would then all need their own tailor-made components. Even if hypothetically every plugin did add those, creating a "disable this entity" behavior would be stupidly complex and might as well be impossible. You would need to register all of these components, track if they existed before, insert them when you try to disable the entity, and remove the ones that weren't already present when you re-enable the entity. You might now also get conflicts with systems that disable specific behaviors, so now every one of these Additionally if apps handle the logic to disable entities correctly, they probably also handle disabling components correctly. Which means we could introduce that feature later and get the "tailor-made" components even if the developer did not implement them. I don't see how forcing the entire ecosystem to either: |
Apologies if I came across as dismissive but that's not my intent -- I'm just trying not to get lost in the details of one specific example when the problem it's trying to solve can be better solved with a simpler approach. I believe that windows are a valid counterexample, as they're a straightforward, simple example of an abstract, non-object entity that we are all familiar with. Windows are not the only cases of abstract entities -- at my work I'm developing an application that is nearly production ready and we are using custom non-object entities for things like user assets that can be imported into a scene. What would it mean to disable an entity like that -- does it get hidden from the user's library but remain a valid asset? Does it become illegal to import it? Does it appear grayed out? None of these seem like an obvious meaning for being disabled so we'd just have to pick something arbitrarily, which is why we have specific marker components for each of these possibilities. |
This really does sound like more of an archetype invariant problem. If it doesn't make sense to disable a Window, or disable an Asset, or disable a SystemId, then you shouldn't be able to insert that component on that entity. I doubt people would actually expect |
The meaning is why you're disabling an entity. Disabling doesn't happen in a vacuum. From there you have to ask is disabling the right tool for something? If the case is "these entities being matched in queries should be the exception rather than the rule temporarily or otherwise" then yes. If however you just want them excluded from specific queries in specific systems then you want markers & filters. Which is why they're more appropriate for your scenario:
People have also been discussing misuses of |
Just because a feature doesn't map to a specific application/use case doesn't mean it's not useful. There are many ECS applications where I'm not using entity disabling & scenarios where the semantics aren't 100% fleshed out. That by itself shouldn't (IMO) block development of a new feature- especially if there is well documented prior art (not just Flecs, but Unity, Godot, Unreal), and there are use cases where it has a clear benefit. FWIW I don't think the |
Given the discussion, this would benefit from a RFC (that will be based on this PR and reference it). From what I could collect, it needs to clarify:
This kind of back and forth is hard to follow and track in a GitHub PR. @NiseVoid do you feel up to write the RFC or would you prefer help or to leave that to someone else? |
I could try to write something up but I'd need some help in making it clearer and filling in some of the details (especially the hierarchy and rendering ones, since those are some of the parts I'm least familiar with). Some more examples would also be useful, so far people here have only brought up loading, rollback and @SanderMertens's laser turret, people probably have more examples. |
More motivating examples:
|
Closing this PR, ideally we'd unblock the usecase for now with #13120, that would allow for experimentation on the user and third-party plugin side and help designing a user friendly API for disabling entities. |
Objective
Solution
retain
Changelog
Disabled
marker component, which hides an entity queries that don't specifically request themMigration Guide
RemovedComponents
, some systems might need to be adjusted to account for this.