-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Basic inherited components/entity prefabs #18767
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
base: main
Are you sure you want to change the base?
Conversation
With this commit, `InheritedComponents` is actually populated whenever a new archetype and table is created, and is updated whenever an entity that is inherited moves archetypes.
It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note. Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes. |
I did some complimentary work over in #17769, working on the "create a library of entities to draw from" pattern using disabling components. |
@@ -5,6 +5,7 @@ | |||
|
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 haven't done a review yet, but this PR seems similar in shape to the Flecs approach, which is cool and worthy of consideration, but I've always been hesitant about it. My biggest concerns about that approach:
- Lookup overhead: data accesses need to do more work to check if a component is inherited or not, and to reach into the correct table. I'd want comprehensive benchmarks of every code path this touches.
- Data access issues: inherited components are "shared data". Our whole access model assumes that if we lock down to the scope of an entity, its accesses will not intersect with other entities. Providing mutable access would violate this naively.
- User interface concerns: especially in combination with (2), this would almost certainly introduce user-facing interface weirdness. How would we write a system that modifies the position of all Transforms, when some of those transforms are inherited? Foisting this complexity on every system author isn't viable, so we would need to internalize it. Would we make
Mut
"smart" and have it check on every access whether it can write directly to the pointer or whether it needs to queue up a command to "clone on write"? That would introduce massive overhead as Mut is very "hot". Do we make only immutable components inheritable? If so then this system becomes effectively useless for prefabs, as we need a prefab system to support the whole range of bevy components.
There are certainly benefits to this model (memory usage and "inherited spawn speed" being big ones). But before taking a single step on this road (in terms of merged features), I'd want satisfying / holistic answers to all of those questions.
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've though about these concerns when working on this implementation, I think it should be possible?
- All entities that inherit components are stored in a separate archetype. This means that looking up which component is actually used needs to be done only once per archetype when iterating. It theoretically should be fast enough if there are many instances of a single entity prefab (and maybe even faster since there is less data that needs to be cached?).
- For this, I think that as long as inherited entities' components are not accessible mutably in the same system as entities which inherit these components, this should still probably work.
- Copy-on-write behavior should be a fairly rare occasion most of the time (it would happen only once) and I hope that we can optimize for the branch predictor to consider this branch cold.
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.
All entities that inherit components are stored in a separate archetype. This means that looking up which component is actually used needs to be done only once per archetype when iterating
This will also add a fixed cost to Query::get
and EntityWorldMut::get
ops right?
For this, I think that as long as inherited entities' components are not accessible mutably in the same system as entities which inherit these components, this should still probably work
This was referring to two entities that both inherit from a third entity both trying to mutably access the same component.
Copy-on-write behavior should be a fairly rare occasion most of the time (it would happen only once) and I hope that we can optimize for the branch predictor to consider this branch cold.
I'm curious to see what Mut COW looks like in practice. There are also corner cases to consider like two queries in a query set that both access the same (inherited) component mutably. How would they coordinate to ensure that both changes are applied / they don't step on each other? Note that structural changes cannot be applied within a normal system.
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 dubious of the overhead and complexity this will introduce, but I'm also very excited and curious to see where this lands!
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 will also add a fixed cost to Query::get and EntityWorldMut::get ops right?
Yes, there's a flag for the archetype and the lookup is done only if the archetype contains any inherited components, which is what flecs does.
There are also corner cases to consider like two queries in a query set that both access the same (inherited) component mutably. How would they coordinate to ensure that both changes are applied / they don't step on each other? Note that structural changes cannot be applied within a normal system.
We can make a staging hashmap for inherited components that have been mutated during system runtime and return them if they're requested multiple times in the same system, if I understood your question.
Been stuck in optimization hell staring at assembly code and llvm ir while implementing shared mutable components, but I've finally managed to implement the basic version of this. Here are the results of my struggles: Iteration benchmarks
The results are... not too bad, I think. There is a clear regression for wide queries, but smaller queries seem to perform just fine. It might be possible to optimize wide queries, but I doubt it. Looking at the generated assembly, there is a branch on each access ( This implementation uses component_id + table_id/archetype_id as a key for the staged mutated components hashmap that stores the cloned components on I need to clean up the implementation a bit before I push the changes here, but overall I think there might be a path forward for this feature (if we're ok with taking the hit for wide queries). |
Objective
This PR adds an implementation of inherited components and entity prefabs. This is useful for creating dynamic entity templates and allows component sharing, which reduces memory pressure when there are many entities with similar components. Some of the prime example of where this is useful are entity-per-tile tilemaps and ecs-based physics implementations.
Solution
Any entity can add an
InheritFrom
component to opt-into inheriting components from the target entity.Any changes to base entity will affect all entities inheriting from it as well. Inherited components are stored only on the base entity, so all entities inheriting a component from the base entity share the same component data.
Getting a shared component is supported only for non-mutable references for now, trying to get a mutable reference to a shared component will result in the same behavior as if requested component doesn't exist on the entity.
Implementation
The implementation is split into several commits, each containing a logical chunk to make reviewing it easier.
There's a new field on world,
InheritedComponents
, that has all the data needed to resolve inherited components for archetypes/tables. If archetype/table has anInheritFrom
component, it is opted-into resolving inherited components during iteration.Most of the functionality is active only on tables/archetypes that have inherited components, so this shouldn't impact performance of normal queries too much. However, iteration performance benchmarks seem to suffer anyway and need more investigation.
One other problem is with systems that have queries working with both inherited components and base components - this system should conflict, but it doesn't right now:
If anyone has any ideas about how to fix this problem and the iteration performance, it'd solve most of the blockers for this PR (from my perspective).
Testing
Added tests for all the basic functionality.
Not all test pass yet, and there might be more cases that need testing.
Future work
This implementation implements only the basic parts of component inheritance, some important stuff that's still missing: