-
-
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
ComponentHook
based Parent
/Children
Management
#15635
ComponentHook
based Parent
/Children
Management
#15635
Conversation
Added new generalised relationship components for One-to-One and One-to-Many relationships. `Parent` and `Children` are now a particular case of a One-to-Many relationship (`OneToManyOne<Family>` and `OneToManyMany<Family>` respectively). With the hooks, users are able to add or remove these components from the ECS while ensuring the appropriate invariants are upheld _at the next sync point_. This is crucial because hooks can _not_ insert or remove these relationship components immediately, they must be deferred. Since the current system requires users to use builder methods and commands anyway, this doesn't change the status quo negatively.
{ | ||
let children = world.get::<Children>(grandparent_entity).unwrap(); | ||
let children = world.get::<Children>(grandparent_entity); | ||
assert!( | ||
!children.iter().any(|&i| i == parent_entity), | ||
children.is_none(), | ||
"grandparent should no longer know about its child which has been removed" | ||
); | ||
} |
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 want to explicitly call out that I believe this test was wrong before this PR, since it left an empty Children
component on the grandparent
entity.
|
||
impl<R: 'static> OneToOne<R> { | ||
fn associate(mut world: DeferredWorld<'_>, a_id: Entity, _component: ComponentId) { | ||
world.commands().queue(move |world: &mut World| { |
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 associate
and disassociate
methods will always defer actions onto the command queue, even if it's possible to act immediately. The reason is to ensure all actions are properly ordered.
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.
Quick passover. Also, this PR indirectly addresses #15270
Co-Authored-By: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
Co-Authored-By: Christian Hughes <9044780+ItsDoot@users.noreply.github.com>
Co-Authored-By: Tamás Kiss <20684057+eidloi@users.noreply.github.com>
You still need a type to cast to which you can't do if the component itself it private. Unless there's something I'm missing. Either way it's significantly harder to accidentally do. |
My two cents on this:
I agree 😉
There is one important reason why, and that is of the interface for using these. A one-to-many relationship component derefs to
While I agree it is safer, I personally think It's also closer to what The downside here is of course that if these are exposed, then they become a part of the public API, so then it's harder to change the implementation of relations while keeping the same API. Private components would be much easier to change.
While I do understand where you are coming from, I'm not sure I agree on all points. This is a larger discussion, but quickly a couple of points:
|
Full transparency: I haven't personally used Aery. I'm coming at this from the perspective of someone who works with relational databases (MSSQL, etc.). Looking at Aery, it's definitely far more feature-complete than what I'm proposing here. What I will say is it's not my intention to match (nor exceed!) what Aery offers. And it is certainly not my intention to ship a half-baked dead-end feature that will only add to migration pains once MVP relations lands in Bevy. In my opinion, the features that I am adding:
Are all fundamental to any relations feature Bevy eventually ships (so much so that we already have
It is possible to refactor my current PR to make the relationships have runtime validation of these invariants instead of compile-time, but I don't think I understand why you would want this. If you don't want these invariants managed, you can use Looking at
I do agree with this idea, which is why all mutable methods provided for these edge types require owned (
If the above guarantees are insufficient, we can adopt the techniques you've outlined (private components,
Not a major point for or against, but I think it's self-evident that this PR does reuse existing ECS tooling, since it is exclusively confined to the
I personally don't see how this PR creates a migration issue any worse than any other PR added to Bevy; it introduces 4 components which should be trivially replaceable once Relations lands. Further, they don't need to be removed to land relations; they can be marked as depreciated and left as-is for as long as Bevy supports
Again, I'm only introducing 4 components which could be moved to wherever the
I personally disagree with this conclusion. I think at most, we should put this behind an
I agree with these points, and I do believe this PR addresses these concerns in ergonomics, reliability, and performance.
Addressed above. I'm not against this, but as others have mentioned, this creates issues for BSN and I really want to stress that I greatly appreciate you taking the time to review this PR. I disagree with some of your points but I am absolutely willing to change this PR if it's the general consensus that what you're proposing is the correct direction for Bevy. I am pushing back because I have seen several instances of people requesting this functionality as-presented, and I genuinely believe it is a positive direction to take Bevy, even if it's only for one or two releases. |
The correctness here is actually worse because you can have both
You can have a bundle API with a hook instead of a command if you really want. Either way the edge types should be private. Sacrificing upholding edge invariants for a slightly different method call is weird especially looking at your previous comment. The energy spent on upholding correctness is misplaced.
No it's not. Archetype fragmentation is a different tool entirely. You cannot do many query operations when edge information is not exposed at an archetype level.
No it won't. You're either imagining only top down edge scenarios or you have the worst case scenario. If you have a relation like If you instead have the actual worst case scenario and lots just of relations this is already the case in mainstream OO engines which perform more than fine. Non-fragmenting relationships aren't mutually exclusive to fragmenting relationships either but they're best not implemented like this PR or Aery. The way flecs does them iirc is with sparse components. |
I think you don't understand what an MVP is. An MVP is not complete functionality it is a minimal viable product. MVP relations will probably just be stuff like
Consistency is more important here than options. Why should users have to care about whether or not the information is erased & if it's not need to know that information every time they want to operate on edges? For what benefit? Being at a type level is not a benefit it is the option.
That is not the same. That is different sides of an edge not entirely different relationship types.
The scenario that isn't covered was already mentioned let e = world.entity(e0).get::<Parent>().unwrap().get();
*world.entity_mut(e1).get_mut::<Parent>().unwrap() = Parent::new(e);
That's not what I'm referring to & as mentioned in my previous response to @nakedible non-fragmenting relationships are best not implemented like this. You will end up with entirely different APIs to do queries for fragmenting & non-fragmenting relations.
It's not just replacing the component types it's also replacing all of the accompanying code doing any kind of operations on entities which will look very different. You will also have people relying on the fact that these are non-fragmenting which is just a complete break instead of a migration. |
Sorry I don't think I made my point very clear here. What I was trying to convey is that a Bevy // not valid code, representative only
let [parent] = e0.get::<Parent>() else { unreachable!() }; Is a terrible user experience. As such, the MVP for relationships must support defining a one-to-many relationship to be able to replace the current As for including Knowing that a relationship is one-to-one, one-to-many, etc. is also a perfectly reasonable assumption to make of the user in my opinion. If the user doesn't know what the relationship is, what could they meaningfully write with them? Again, if they don't care, a many-to-many is always there. |
That's more to do with the way hierarchy is implemented. When you have components for edges on entities ofcourse the entities mean something different depending on if you're at the base or head of an edge. With proper relationships we will only have
I do. This is just erroneous & I would block on this if I had the power. When would it ever make sense to have both? It is not useful to just provide more ways to do things. You need to provide ways to easily do things that make sense that people actually want to do.
You're contradicting yourself here. It can't both be that "they should always know this information" & "but I erased the information they should always know in every example". It's not just needing to know it's also having to restate it every time. Redundant repetition is a big source of errors in programming. |
@iiYese I'll answer to the rest a bit later, but:
Can you list the query operations that require and archetype based relations implementation, as opposed to component based? |
Some of the important ones off the top of my head:
|
Co-Authored-By: iiYese <83026177+iiYese@users.noreply.github.com>
Based on pushback on the functionality provided here I have removed all new functionality from this PR with the exception of hook-based management of |
ComponentHook
based RelationshipsComponentHook
based Parent
/Children
Management
I'm sad that this is the outcome, but on the other hand, I do believe it's the right outcome for this pull, so that the non-controversial parts are not gated behind the controversial parts. However, I would suggest that there is a follow-up pull which would reinstate the removed generic relations functionality behind an experimental flag. Then we can discuss those in isolation without combining it with the fixes to the current Parent/Children functionality. Also, I don't think this pull is the right place to discuss @iiYese 's objections – so I'll take to answering to those elsewhere. |
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.
So a lot of the relationship stuff is still there which becomes unused complexity that has to be maintained. The component types are still public which is the last thing that allows breaking invariants. I would suggest:
- Getting rid of all the remaining relationship stuff. Traits, generics etc. turning
OneToMany<R>
intoChildrenInner
with an accompanyingParentInner
that are private. - Making immutable
QueryData
structsChildren
&Parent
for easy migration.
This PR got quite big & is trying to address multiple issues at once. Before this discussion it was also trying to shoehorn new features in with the PR that were unrelated to the issues. In future keep the PR small & focused on a single issue & make different PRs for new features. Small PRs are easier to review & merge & will save you from these kinds of head aches.
Closing as I don't see any reason to continue changing this PR. Based on feedback, a more generally accepted solution would be to take the I personally disagree with this approach, so I would invite someone else to take over from here who has a resolute belief in this approach. Thanks again to everyone who reviewed this PR, and I apologise I couldn't see it through. |
Okay, now it became the saddest outcome 😭 |
Missed this initially. Disappointed this ended up getting closed. I'd like to float this by the ECS SME team. @alice-i-cecile @JoJoJet @maniwani do yall think you could give this some consideration after the 0.15 release? |
To summarize:
My broad position is that I'm fine with a very slow initial relations implementation, but we should try to target the ideal correctness/API design to avoid confusion. Moving incrementally towards that is welcome, and prototyping with just Parent/Children is going to be easiest :) |
Objective
Parent
/Children
from the world and then reinserting them can break hierarchies #15575Parent
+Children
hierarchy code simpler and more robust #12235Children
that mutate toEntityCommands
#15270Solution
Parent
andChild
specific types of a One-to-Many relationship (relationship typeFamily
)Testing
Benchmarks
I ran the
despawn_world_recursive
benchmarks on my laptop and saw a 25% - 35% performance improvement moving to aComponentHooks
based relationship system in this PR.despawn_world_recursive/10000_entities
I also used
tracy
to check thatalien_cake_addict
(my go-to example of a "real" game in Bevy) performed well.alien_cake_addict
tracy
fame-time plotWhile
alien_cake_addict
doesn't use the hierarchy extensively and explicitly (I think just for the score board at the end), I still think it's important to include.Showcase
Users can now directly insert
Parent
onto child entities without the need for the child-builder methods.Recursive despawn is now also offered in a generalised form operating on any
Component
which implementsVisitEntities
.Note that currently this recursive despawn can only traverse a single relationship type.
Migration Guide
HierarchyEvent
The
HierarchyEvent
type has been replaced. The following mapping from old to new is required:ChildAdded
->Added
ChildRemoved
->Removed
ChildMoved
->Removed
and thenAdded
in successionDespawnRecursive
/DespawnChildrenRecursive
/despawn_with_children_recursive
DespawnChildrenRecursive
has been removed and replaced with a parameter,inclusive
, on the existingDespawnRecursive
. The oldDespawnChildrenRecursive
is identical in functionality to the newDespawnRecursive
with the parameterinclusive: true
.despawn_with_children_recursive
has been removed to reduce the public API surface area. Instead, construct aDespawnRecursive
command and useapply(...)
from theCommand
trait to immediately invoke it on aWorld
.DespawnRecursive
's fields are now private. Usenew
and the appropriatewith_x
methods to change parameter defaults.DespawnRecursive
now has a type parameter,C
, which controls which relationship to follow recursively. UseChildren
to match the previous behaviour.Notes
In previous iterations of this PR, the possibility of generalised relationships was included, including one-to-one and many-to-many. These were removed during the review process based on feedback from subject matter experts. They may be re-added once MVP relationships lands at a TBD date. See #3742 for further details.