|
1 | | -<!-- Relationships (non-fragmenting, one-to-many) --> |
2 | | -<!-- https://github.com/bevyengine/bevy/pull/17398 --> |
| 1 | +When building Bevy apps, it is often useful to "link" entities together. The most common case in Bevy is connecting parent and child entities together. In previous Bevy versions, a child would have a `Parent` component, which stored a reference to the parent entity, and the parent entity would have a `Children` component, which stored a list of all of its children entities. To ensure these connections remained valid, developers were not allowed to modify these components directly. Instead, all changes had to be done via specialized commands. |
3 | 2 |
|
4 | | -<!-- TODO --> |
| 3 | +This worked, but it had some pretty glaring downsides: |
| 4 | + |
| 5 | +1. Maintaining hierarchies was "separate" from the core ECS data model. This made it hard to improve our spawn APIs, and made interacting with hierarchies less natural. |
| 6 | +2. The system was specialized and not reusable. Developers wanting to define their own relationship types had to reinvent the wheel. |
| 7 | +3. To ensure data integrity, expensive scans were required to avoid duplicates. |
| 8 | + |
| 9 | +In **Bevy 0.16** we have added initial support for **relationships**: a generalized and efficient component-driven system for linking entities together bidirectionally. This is what defining a new [`Relationship`] looks like: |
| 10 | + |
| 11 | +```rust |
| 12 | +/// This is a "relationship" component. |
| 13 | +/// Add it to an entity that "likes" another entity. |
| 14 | +#[derive(Component)] |
| 15 | +#[relationship(relationship_target = LikedBy)] |
| 16 | +struct Likes(pub Entity); |
| 17 | + |
| 18 | +/// This is the "relationship target" component. |
| 19 | +/// It will be automatically inserted and updated to contain |
| 20 | +/// all entities that currently "like" this entity. |
| 21 | +#[derive(Component, Deref)] |
| 22 | +#[relationship_target(relationship = Likes)] |
| 23 | +struct LikedBy(Vec<Entity>); |
| 24 | + |
| 25 | +// Later in your app |
| 26 | +let e1 = world.spawn_empty().id(); |
| 27 | +let e2 = world.spawn(Likes(e1)).id(); |
| 28 | +let e3 = world.spawn(Likes(e1)).id(); |
| 29 | + |
| 30 | +// e1 is liked by e2 and e3 |
| 31 | +let liked_by = world.entity(e1).get::<LikedBy>().unwrap(); |
| 32 | +assert_eq!(&**liked_by, &[e2, e3]); |
| 33 | +``` |
| 34 | + |
| 35 | +The [`Relationship`] component is the "source of truth", and the [`RelationshipTarget`] component is updated to reflect that source of truth. This means that adding/removing relationships should always be done via the [`Relationship`] component. |
| 36 | + |
| 37 | +We use this "source of truth" model instead of allowing both components to "drive" for performance reasons. Allowing writes to both sides would require expensive scanning during inserts to ensure they are in sync and have no duplicates. The "relationships as the source of truth" approach allows us to make adding relationships constant-time (which is an improvement over previous Bevy versions!). |
| 38 | + |
| 39 | +Relationships are built on top of Bevy's [Component Hooks](/news/bevy-0-14/#ecs-hooks-and-observers), which immediately and efficiently maintains the connection between the [`Relationship`] and the [`RelationshipTarget`] by plugging directly into the component add/remove/update lifecycle. In combination with the new [Immutable Components](#immutable-components) feature (relationship components are immutable), this ensures data integrity is maintained no matter what developers do! |
| 40 | + |
| 41 | +Bevy's existing hierarchy system has been fully replaced by the new [`ChildOf`] [`Relationship`] and [`Children`] [`RelationshipTarget`]. Adding a child is now as simple as: |
| 42 | + |
| 43 | +```rust |
| 44 | +commands.spawn(ChildOf(some_parent)); |
| 45 | +``` |
| 46 | + |
| 47 | +Likewise reparenting an entity is as simple as: |
| 48 | + |
| 49 | +```rust |
| 50 | +commands.entity(some_entity).insert(ChildOf(new_parent)); |
| 51 | +``` |
| 52 | + |
| 53 | +We also took this chance to improve our spawn APIs more generally. Read the next section for details! |
| 54 | + |
| 55 | +Note that this is just the first step for relationships. We have plans to expand their capabilities: |
| 56 | + |
| 57 | +1. Many-To-Many Relationships: The current system is one-to-many (ex: The `ChildOf` relationship points to "one" target entity and the `RelationshipTarget` can be targeted by "many" child entities). Some relationships could benefit from supporting many relationship targets. |
| 58 | +2. Fragmenting Relationships: In the current system, relationship components "fragment" ECS archetypes based on their _type_, just like a normal component (Ex: `(Player, ChildOf(e1))`, and `(Player, ChildOf(e2))` exist in the same archetype). Fragmenting relationships would be an opt-in system that fragment archetypes based on their _value_ as well, which would result in entities with the same relationship targets being stored next to each other. This serves as an index, making querying by value faster, and making some access patterns more cache friendly. |
| 59 | + |
| 60 | +[`Relationship`]: https://dev-docs.bevyengine.org/bevy/ecs/relationship/trait.Relationship.html |
| 61 | +[`RelationshipTarget`]: https://dev-docs.bevyengine.org/bevy/prelude/trait.RelationshipTarget.html |
| 62 | +[`ChildOf`]: https://dev-docs.bevyengine.org/bevy/prelude/struct.ChildOf.html |
| 63 | +[`Children`]: https://dev-docs.bevyengine.org/bevy/ecs/hierarchy/struct.Children.html |
0 commit comments