-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Marker component discoverability #3833
Comments
So, personal preferences time.
In general, I'm nervous about the more bundle-centric solutions: it pushes away from the core appeal of the ECS (a fully compositional set of components), and is generally less robust. |
As an extra benefit: marker components allow users to use addition / removal detection on the components, rather than having to track the previous state (change detection alone is not sufficient, since the value could have been mutated without changing). |
Based on my reasoning:
|
@MiniaczQ your solution is totally the way to go. Thanks! Concrete strategy:
|
My take is that this should probably just be additional "metadata" attached to either Bundles or collections of components. Here are some more proposals: Proposal 7: Additional Bundle metadata#[derive(Bundle)]
#[bundle_supports(NotShadowReceiver)]
struct MeshBundle {
mesh: Handle<Mesh>,
global_transform: GlobalTransform
}
// bundle trait would be extended to include Bundle::get_supported_components() Pros: simple. self-documenting. relevant information is paired directly with other bundle components + easily discoverable Proposal 8: Decoupled Component Composition Metadataapp.add_supported_component_composition::<NoShadowReceiver, (Handle<Mesh>, GlobalTransform)>() Pros: self-documenting (for people reading code), decentralized (downstream users can define new supported compositions, can add their components to upstream "bundles"), doesn't require expanding the the Bundle scope like (7) ... if an entity has the right component composition, it will "support" the given component. Proposal 9: Compositions defined on Components#[derive(Component)]
#[supported_composition(Handle<Mesh>, GlobalTransform)]
struct NotShadowReceiver; Pros: self-documenting, centralized + discoverable (all supported compositions are easily discoverable in one place) In order of preference: (8), (7), (9). |
My feedback on those:
I'm guessing the provided components are the minimal set, so actual queries can take more, right? |
Yup! I guess its also worth calling out that (9) and (8) are compatible with each other. We could encourage (9) generally, but support (8) if we find cases that need it. |
Would it be possible to use both 8 and 9? As in, centralize the definition around the marker upstream, but still allow for extensions downstream. |
Proposal 10: Extensible Compositions defined on Components#[derive(Component)]
#[supported_composition(Handle<Mesh>, GlobalTransform)]
struct NotShadowReceiver;
app.add_supported_component_composition::<NoShadowReceiver, (FancyShadowConfig)>() A hybrid of 8 and 9, enabling non-upstream extensibility without sacrificing the benefits of proposal 9. Current preferencesIn order:
I think we should do 2, but not to solve this problem. |
I like 7 the most, the objective is not to make optional marker components part of bundles but to have an indication of which components are "conceptually" working together. |
I do want to stress that there is a reasonable chance that bundles will be elevated to first class citizens / expand in scope to accommodate prefabs (ex: add lifecycle hooks, omit default component values in scenes, ensure bundles stored in older versions of scenes support components added to the bundle in newer versions). Very much TBD, but relevant nonetheless. It might cease to be a "con". |
But I still think (10) has my vote in the interim. |
Sounds good. We can move forward with 10, and toss in 6 as a separate feature (I think it's very useful regardless, but solves a slightly different problem). |
Coupling components together seems like it would make composing components from third party plugins difficult. It would also need rules like rust traits, only the implementer of a component type or a relationship can specify a relationship, otherwise you end up with potential conflicts. I'm not a huge fan of using Bundles to inform relationships; I find myself re-implementing my own version of bevy-native bundles (e.g. PbrBundle) because I need to swap out a single component to change the functionality of an entity. Using bundles tends to make component composition more difficult, and I'd prefer not to give them more "power" than they already have. Alternatively, this relationship metadata could be coming from systems - that's where these relationships are used, right? Documenting relationships is redundant information: relationships are already specified by Extending this line of thought, we could specifically document ZST components by looking at what components are being queried with |
Some more thoughts on proposal 8 and all the ones that rely on it. If possible it'd be good to make use of newtype pattern, so that downstream can 'inherit' the original marker. |
Proposal 11Implement proposal 6, but then further refine the list to reduce false positives. In the editor, split all component lists into ZST and non-ZST sublists. Discussion
I very much agree with this. When attempting to discover "what else can I do with this component", what I want is "here is a list of active systems that act on this component", and "here is a list of components used in those systems". As a doc writer, I'm going to be frustrated by constantly trying to keep this information in sync. As a user, I'll be frustrated when this information is wrong: either as a false positive or false negative, due to out-of-date docs or inaccurate assumptions about the state of my app. Ideally we'd have IDE and rustdoc support for this, but that would require a lot of external support. I think proposal 11 achieves the same goals as your proposal @aevyrie. ZSTs and non-ZSTs serve two very different roles in the Bevy ecosystem. Splitting the list in a consistent, collapsible fashion will allow me to quickly parse "what does this component do", and "which components can I add / remove to toggle behavior". When examining the associated components for a ZST, we can skim over the ZST sublist and vice-versa. |
Just to echo @MiniaczQ as well as @aevyrie One problem with 10 is that it provides two different ways of doing the same thing.
Only supporting |
I fail to understand how defining it at |
I agree with that, everything else being equal. The ergonomics of 8 are worse than 9 though, and because it's runtime you're forced to rely on an external tool anyways. |
Doesn't work unfortunately. If we have struct Upsteam;
struct Downstream(Upstream);
|
One more thing thats worth pointing out: while this is important to solve for the editor, I don't think we need to prioritize an implementation until then. Once we reach agreement on a plan, I think that can unblock adopting the marker component pattern more widely in Bevy crates. |
We also might want to implement both (11) and (10). I'm worried about noise / false positives from (11). And manually filtering out noise could result in a lot of code noise, in itself. But given that the information is already there, the impl shouldn't be too hard. (10) is also relatively trivial to implement. Once we have an editor, we could try enabling (11) and (10) in a complicated code base and evaluate the "supported component" experiences. |
That plan by itself is satisfying enough for me. We have two workable solutions and we can compare them in practice when the time comes. |
In the interim, we could just throw "supported marker component" docs into relevant bundles. |
It makes component composition dynamic and something that can be configured. E.g. if it's defined on the marker with an attribute, there would be no obvious way to opt out. As this feature is for an editor, seems useful to limit what components can be added on a custom bundle. If instead
that could be configured depending on needs. Say we have "enable behavior" by convention:
I am thinking that ideally it should not be an option to add |
This is based entirely on my understanding, correct me if I got it wrong. I don't think this was ever about limiting the ways markers can be applied? Another thing is, we abstracted from bundles. The composition rules provide the minimal set of components that need to exist in a query, for a marker to be applicable. There is no blacklisting specific bundles despite them satisfying the condition. The 'enable behavior' seems irrelevant, so I'm skipping over it. Also not super relevant, but I don't think a bundle should be deriving from |
Yes, my apologies for not having a clearer example -- the fact that it's a bundle is irrelevant. Only that there is an entity that has body A solution which would allow controlling what compatible compositions are exposed for a particular entity, I thought it'd be easier when avoiding attributes on a type vs. defining at runtime what's allowed.
That was just to make the example work, e.g. otherwise
Oops, indeed 👍 -- left as I refactored the example |
This would make entities store additional metadata and needlessly restrict the usability. |
Right, there would need to be some way to differentiate at the runtime between the entities, I see what you mean, makes sense |
My thinking on this isn’t so clear but while considering 6 I got some feeling that it misses something. In the renderer we have disjoint queries on views that have semantics about meshes. We’re discussing for example a Visible (or similar) marker component on entities with meshes and that impacts other queries about views (VisibleEntities). My gut felt like while you may be able group markers on entities according to how they are queried, is there something missing in general about the intent or consequences of the marker component as a system practically ties multiple queries together? And then we have systems which do multiple queries which feel like they should be handled in disjoint systems but for reasons (like needing knowledge about when some component is added/removed on some entity to take its own decisions about what to do and splitting systems would mean Commands syncs being necessary between them which is not so easy currently), they are handled in the same system. I think what I’m getting at is that, as a user, when looking at an entity and seeing availability of a market component that was discovered due to being part of a query, I know very little about the effects or consequences of enabling/disabling it. It could easily be noisy and create usability problems in that it’s information overload or the purpose of the market component is misunderstood and they toggle it and unintentionally break stuff. And documenting this at the market component definition site may not be possible as the extensibility through custom systems can as all kinds of side effects that are completely unpredictable. I’m not totally clear whether I have a legitimate point here but hopefully someone who understands better can judge that. :) |
In an editor or code IDE you could present curated results from 10 ahead of ‘searched’ results from 11. Results from 10 could be bold and from 11 not. Etc |
If I've understood this issue correctly, it's about how marker components are "suggested" by the editor when composing entities. I'll be contrarian and say that I think this is actually fundamentally the wrong approach. Components should - mostly - not be surfaced in the editor. On a larger team, it's important to separate the responsibilities of engineers and designers. Level design and engineering work should be able to be iterated on simultaneously and asynchronously with minimal breakage to each other. The editor is used to specify the intended behaviour, with little care for how that behaviour is implemented. It specifically does not want to be tied to the underlying ECS data layout, for two reasons.
I'll throw my own proposal onto the pile.
In this setup, there's no need for marker components to be surfaced in any particular way. To continue from the examples, a Mesh Author would simply include a Notably, this is the same conclusion that Unity have recently reached. As their CTO, Joachim Ante said:
This is relevant to this discussion, but perhaps it's also its own discussion. I can create a separate issue or discussion for it if desired. |
Definitely worth bringing up! I do think how runtime components map to "editor representation" and "scene representation" is worth discussing (and I think your proposal does deserve its own discussion). From a certain perspective, I think its similar to the other suggestions, as it involves correlating entities of a certain "type" to other "potentially available" components. Therefore I don't think making a final call on that has bearing on whether or not we start using marker components today (as your suggestion also solves the problem at hand). We now have even more solid paths forward :) |
I've created a dedicated and more detailed issue for my authors proposal. #3877 |
Closing in favor of #14946, which should solve this well. |
Problem
Marker components (zero-sized structs used only for filtering) are very useful and natural. However, they have a few problems that make them hard to use in the engine. From @cart in #3796:
Potential Solutions
i. Query filters are faster than manually checking values. This is important for e.g. core rendering algorithms.
ii. Query filters are less blocking than manually checking values.
iii. Query filters are clearer than manually checking values, as the information is encoded in the function signature.
iv. The issues raised above will also apply to end user games, so this is not really a solution.
ShadowCaster
, notNotShadowCaster
.i. Improves discoverability: most relevant behaviors will be enabled by default.
ii. Improves clarity by avoiding double-negative reasoning.
iii. Will very slightly increase overhead in cases where the behavior is a default.
iv. Does not solve visual editor problem, as there's no way to tie entities back to their original bundles.
v. Does not fully solve discoverability problem, as not all interesting behavior is on-by-default.
i. Solves the general discoverability problem, but only for plugin/engine components.
ii. Does not solve the visual editor problem, since we don't know which bundles an entity is made up of.
iii. May have non-trivial performance overhead for spawning entities with optional bundle fields.
iv. Almost certainly requires Make a dynamically applicable version of
Bundle
#3694 to be merged.i. More powerful with 2 and 3, solving their visual editor problem.
ii. Store this information at entity creation time, or compare the components to a registered list of bundles.
i. Hard to display in the docs.rs docs, unless by some miracle we can do this all at a type level.
ii. Provides a clear, general mechanism for discovery in both cases.
iii. Discoverability is user-extensible.
i. Like 5, but way easier and automatic.
The text was updated successfully, but these errors were encountered: