Repository navigation
Description
Overview
Inspired by flecs, relations are a first-class graph primitive for the ECS: useful for modelling generalized hierarchies, graphs and so on in a safe, fast and ergonomic way by working with the ECS, rather than fighting it.
These have three main categories of benefits:
- Ergonomics: query for entities with specific relation types and iterate over the various relations
- Correctness: enforce critical logic invariants (such as "each child must have a parent" or "each child can only have at most one parent")
- Performance: operate more quickly, especially compared to naive hand-rolled implementations
Challenges
There are several critical challenges that need to be addressed by any design:
- Performance: ECS is designed for random-order linear iteration
- Serialization: entity ids are opaque and ephemeral
- Ergonomics: there's a lot of interesting and important things you may want to do
- Graph structure: how do you ensure that the rules of the graph that you want to represent are enforced upon creation, and stay correct as components and entities are added and removed. Some graphs may be directed, some may be symmetric, some may have edge weights, some may be acyclic, some may be trees, some may be forests...
- Multiple edges from / to the same entity: this is an essential use case, but entities can (currently) only have one of each component type.
Possible paths forward
Essential universal tasks:
- Thoroughly benchmark existing tools and strategies for working with graphs of entities
Optional universal tasks:
- Allow entities to store multiple components with the same type #1527
- Investigate further optimizations for
query.get() - Allow
Option<Entity>to use memory layout optimization #3022
Index-driven approaches:
- Create one or more reasonably performant graph-storing indexes
- Implement a complete, ergonomic indexes solution (see Fast Component Lookup-by-Value with Indexes #1205)
Broadly speaking, there are two paths to this design:
- Index-based: Store the graph in a resource, keep that resource up to date. Use that resource to ensure constraints aren't broken, and enable fast look-up.
- Baked-in: Reframe components as relations with no targets. Reshape the ECS to support relations as the first-class primitives.
In both cases, these implementations details would be hidden to the end user.
Ultimately, we want to build an API that broadly follows the one outlined in bevyengine/rfcs#18 if at all possible.
Atomic indexes
- Make the schedule less of a nightmare using Stageless: a complete scheduling overhaul rfcs#45
- Implement atomic groups ala Atomic schedule groups rfcs#46
- Implement a full indexing solution, using the automatically scheduled cleanup systems seen in Fast Component Lookup-by-Value with Indexes #1205.
Permissions and hook indexes
- Create a permissions and hooks framework, which only allows certain components to be accessed with the appropriate components, and can do cleanup work on component removal (see this early design doc)
Non-fragmenting relations
- Revive Entity Relations #1627
- Alter the internals so the indexes no longer fragment archetypes by default
- As a result, remove the ability to quickly filter by target
Related work and issues
This topic was discussed at great length in bevyengine/rfcs#18. The consensus there was that the feature was incredibly useful and desired, but the specific approach taken in #1627 was challenging to implement and review, and had serious non-local performance impacts due to the archetype fragmentation created.
Entity groups (#1592) reflects one possible use case of this feature.
#2572 covers a broad laundry list of complaints and limitations of the current frustrations with Parent-Child hierarchies, especially in the context of UI.
Kinded entities (#1634) are a very useful correctness feature when working with relations extensively, to help verify that you're pointing to the flavor of entity you think you are. They are probably blocked on #1481.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
nicopap commentedon Jan 22, 2022
This is relevant to bevyengine/rfcs#41 since the navigation system builds on a DAG stored in the ECS.
It might be an interesting starting point for discussions around the topic. Implementation can be a clue at what a system that relies heavily on relationships can look like https://github.com/nicopap/ui-navigation
I think relations could contribute to improving the ui-nav lib, for example:
TheRawMeatball commentedon Jan 27, 2022
The atomic indexes solution is not as viable as the other options because &mut World based editing could still trivially get broken state. While that could be acceptable for some use cases, I don't think it is for most.
SanderMertens commentedon Jan 28, 2022
Was discussing an approach to relations in Bevy with @alice-i-cecile yesterday. Thought I'd post it here as it's not very complicated, and could be a quick way to get relations support.
Pros:
Limitations / TODOs:
The idea (in pseudo, I don't know Rust well enough):
DrewRidley commentedon Jun 24, 2022
I think this post here provides a good reference as to why relationships would be important.
It also provides some implementation details that could be useful.
https://ajmmertens.medium.com/building-games-in-ecs-with-entity-relationships-657275ba2c6c
Hierarchy commandization (#4197)
Hierarchy commandization (bevyengine#4197)
Diddykonga commentedon Oct 5, 2022
Psuedo-Impl for Relations, as One Kind Struct, and Two (Entity/Component/System/Relation) Refs:
Example:
There are clear issues with this impl, but is still useful to convey the ideas of what relations can be.
Hierarchy commandization (bevyengine#4197)
Zeenobit commentedon Oct 31, 2022
I've been working on an alternative implementation of entity relations as a product of trying to solve Kinded Entities:
#1634 (comment)
The comment provides more details. In summary, I believe entity relations and kinded entities are two interwoven solutions to the same set of problems (i.e. expressing relationships between entities in a safe and readable way).
I've solved this breaking the problem into two main chunks:
Component Links: https://gist.github.com/Zeenobit/c57e238aebcd4ec3c4c9b537e858e0df
Connections and the
relation!macro: https://gist.github.com/Zeenobit/ce1f24f5230258d0cf5663dc64dc8f2bZeenobit commentedon Nov 1, 2022
Using the implementation above, I think we can express entity relations in this way:
Some permutations that I think are possible:
In English:
So far, I've managed to get 3 and 6 working as a proof of concept.
Using the same logic, we could express 1-to-1 and many-to-many relationships as well:
I suspect 1-to-1 and many-to-many relationships are a little more tricky though, just because I can't specialize an impl where two generic A and B are not the same, so Rust might get confused in trying to figure out which type is connectable to which other type.
Zeenobit commentedon Nov 2, 2022
I've managed to get all of the following permutations functional:
Full gist with examples/tests at the bottom for each case:
https://gist.github.com/Zeenobit/ce1f24f5230258d0cf5663dc64dc8f2b
Note: I haven't included any examples of how systems would use these relations. This is because everything is just components. Any system can query any term in a "relation clause" as a regular component. The user can also further extend each component to serve their specialized needs.
The implementation looks complicated, but it's mostly a lot of copy-pasted macro logic. I'm putting this here with the hopes that someone more experienced with macros can help with simplification and solving the 1-to-1, many-to-many and potentially even fixed capacity (i.e. 1-to-N) relations.
In the meantime, I'm planning to release Component Links and Entity Relations as separate crates. While I think integrating them into Bevy actual could make things more ergonomic, I also see benefits in having it be proven in the wild as a separate crate.
@alice-i-cecile Are there any objections to me using
bevy_ecs_relationandbevy_ecs_linkas crate names?alice-i-cecile commentedon Nov 2, 2022
I'm looking forward to seeing these experiments!
Generally speaking, it's nicer to avoid using
bevyin your crate names here, especially at the start. For example, my input crate isleafwing_input_manager. It's better to make it clear that it's for Bevy, not by Bevy.iiYese commentedon Dec 30, 2022
I think the items from this discussion would be appropriate to add under "universal tasks"?
Hierarchy commandization (bevyengine#4197)
LegNeato commentedon Sep 12, 2023
TL;DR:
When designing relations it is probably useful to learn from the most successful graph-based product in the world: Facebook. Here is an old paper with their TAO system: https://engineering.fb.com/2013/06/25/core-data/tao-the-power-of-the-graph/. And a blog post: https://engineering.fb.com/2013/06/25/core-data/tao-the-power-of-the-graph/. This API literally has not changed in over 10 years and powers all FB's products. It has been flexible enough to keep up with FB's product, storage, and query needs as they changed.
I think TAO's model maps well to Bevy concepts. The only difference is bevy is currently "decomposed" where instead of fields stored on an entity/object, the are stored on the component:
Entity --[Points to]--> Component --[Stores]--> DataPerson Entity --[Points to]--> Name Component --[Stores]--> Name(String)Object --[Stores]--> DataPerson Object --[Stores]--> Name(String)But, FB's TAO has the concept of "Associations". In TAO you can selectively decompose object fields via associations (and most TAO objects do):
Object --[Stores]--> DataPerson Object --[Stores]--> Name(String)Object --[Points to]--> Association --[Points to]--> Object --[Stores]--> DataPerson Object --[Points to]--> Name Association --[Points to]--> Name Object --[Stores]--> Name(String)There is an extra level of indirection in there vs bevy! Bevy's components are structurally the same as TAO Objects with fields:
Object --[Stores]--> DataPerson Object --[Stores]--> Name(String)Component --[Stores]--> DataName Component --[Stores]--> Name(String)And TAO Objects are structurally similar to bevy Entities. So bevy components are entities with some data attached. They are a node, not an edge in the graph. They only act like an edge in bevy because they are hardcoded in the engine and the storage and query system encourages this thinking.
TAO's Associations as pointers between data are strictly more powerful than Bevy's Components as you can create your own connection types (and those can have data too!). Conversely, in bevy pointers between data (components) are a closed data model / type system.
So I think Bevy should step back and stop making Components "special" and instead have two types: Entities and Edges (name can be bikeshed here). ECS users can create typed Entities and typed Edges. And they are all nodes to the query and storage system.
alice-i-cecile commentedon Sep 12, 2023
The dynamic queries API in #9774 will be a useful building block for relations.
[-]Tracking issue: entity-entity relations 🌈 [/-][+]Entity-entity relations 🌈 [/+]benfrankel commentedon Feb 1, 2024
The flecs link at the top of this issue is broken. I think the new link is https://www.flecs.dev/flecs/md_docs_2Relationships.html.
ryo33 commentedon Apr 4, 2024
I'd like to share my thoughts about entity relation, which I cannot find someone had mentioned. (I prefer "triple" for the term, and used it below.)
If using entity relation for representation of ai agents' knowledge, there are cases relation could also be an target of other relation. For example "my teammates
knows that I know the last position of the enemy". Notice, in this case, the know can target both objects and relations. To support the use-case, we need to keep the relation types also being a first class component in design. Although that, it's still convenient to have relation-kind in components.
Supporting both directional and bidirectional relation may lead extra complexity in implementation. Even If we don't support it, if the cost of using relations are cheap, we can express a bidirectional relation by a pair of directional relation like: "a key is linked to a door, and a door is linked to a key."
Wildcard querying can help garbage collection like things. For example, I'd like to record the reference frequency for triples and remove triples in less frequent access. It can emulate "forgetting". It can be done in a similar way as transform propagation. Instead, since I know all types that will be used in my game, I can maintain a pile of generics systems like
forgetting<Know>,forgetting<Love>,forgetting<HasName>, but it's painful.I'm staring to design queries and relation management systems, for my game and also for a library for general use. To me, the current design/implementation of Bevy ECS looks like already super powerful and beautiful, so I'm trying to figure a way to workaround the build-in relation feature. It might help designing of built-in one.
SanderMertens commentedon Apr 5, 2024
@ryo33 These things have been discussed before:
is mentioned briefly at the end of https://ajmmertens.medium.com/why-it-is-time-to-start-thinking-of-games-as-databases-e7971da33ac3 (search for "theory of mind") and has been discussed on the Flecs Discord. There are a few proposed designs to support this. One is to have an entity with Relationship, Target and Source pairs that point to the different bits of information + some query syntactic sugar to make it nice.
Is the
Symmetricrelationship property: https://www.flecs.dev/flecs/md_docs_2Relationships.html#symmetric-propertyWildcard queries are a fundamental part of how relationship/hierarchy cleanup works.
Relationships (non-fragmenting, one-to-many) (#17398)
Relationships (non-fragmenting, one-to-many) (bevyengine#17398)