Components as entities - #23988
Components as entities#23988Trashtalk217 wants to merge 24 commits into
Conversation
This reverts commit ba5d095.
…ents-as-entities
SkiFire13
left a comment
There was a problem hiding this comment.
In order to get access to a resource entity, you usually need to go through two lookups: TypeId -> ComponentId -> Entity. The ComponentId -> Entity lookup can potentially be removed, speeding up resource lookup.
On the other hand note that you have introduced a HashMap lookup into every ComponentId -> ComponentInfo path! This happens for example when calling hooks, which is arguably pretty common too.
Sidenote: do we have a benchmark for hooks to measure this?
Currently, one big downside of resources-as-components is that any components on resource entities aren't serialized. This forms a barrier for implementing required components for resources. Implementing this correctly is tricky. One of the problems we run into is that IsResource(ComponentId) cannot be directly copied over because ComponentIds are not consistent between worlds.
I'm not sure how this PR solves the issue. You'll still have the issue that the EntityId in the new world needs to match the ComponentId of the resource.
| pub(super) components: Vec<Option<ComponentInfo>>, | ||
| pub(super) components: HashMap<ComponentId, ComponentInfo>, |
There was a problem hiding this comment.
We'll likely want to benchmark this.
|
Given that |
|
The instance of the component on its own id would be the one accessed by That doesn't make resources more powerful, but by making them less special it could simplify our code. |
|
I've added the benchmarks from #24058 and got the following results. If resources are stored with If resources are stored with |
# Objective Part of the bevyengine#23988 and bevyengine#24058 saga. We attempt to speed up resource access. ## Solution When messing around with bevyengine#23988 I noticed that changing the storage type mattered a lot for the benchmarks. ## Testing Added benchmarks from bevyengine#24058 and got the following micro benchmarks compared to main: ``` ecs::resources::get time: [6.3584 ns 6.3840 ns 6.4097 ns] change: [−10.625% −10.075% −9.6652%] (p = 0.00 < 0.05) Performance has improved. Found 9 outliers among 100 measurements (9.00%) 1 (1.00%) low mild 8 (8.00%) high mild ecs::resources::get_mut time: [7.4181 ns 7.4343 ns 7.4515 ns] change: [−39.895% −39.304% −38.809%] (p = 0.00 < 0.05) Performance has improved. Found 7 outliers among 100 measurements (7.00%) 5 (5.00%) high mild 2 (2.00%) high severe ecs::resources::insert_remove time: [89.515 ns 89.654 ns 89.815 ns] change: [−20.163% −16.527% −11.930%] (p = 0.00 < 0.05) Performance has improved. Found 10 outliers among 100 measurements (10.00%) 2 (2.00%) low severe 1 (1.00%) low mild 3 (3.00%) high mild 4 (4.00%) high severe ``` If someone wants to double-check these numbers, I encourage you to do so.
# Objective Part of the bevyengine#23988 and bevyengine#24058 saga. We attempt to speed up resource access. ## Solution When messing around with bevyengine#23988 I noticed that changing the storage type mattered a lot for the benchmarks. ## Testing Added benchmarks from bevyengine#24058 and got the following micro benchmarks compared to main: ``` ecs::resources::get time: [6.3584 ns 6.3840 ns 6.4097 ns] change: [−10.625% −10.075% −9.6652%] (p = 0.00 < 0.05) Performance has improved. Found 9 outliers among 100 measurements (9.00%) 1 (1.00%) low mild 8 (8.00%) high mild ecs::resources::get_mut time: [7.4181 ns 7.4343 ns 7.4515 ns] change: [−39.895% −39.304% −38.809%] (p = 0.00 < 0.05) Performance has improved. Found 7 outliers among 100 measurements (7.00%) 5 (5.00%) high mild 2 (2.00%) high severe ecs::resources::insert_remove time: [89.515 ns 89.654 ns 89.815 ns] change: [−20.163% −16.527% −11.930%] (p = 0.00 < 0.05) Performance has improved. Found 10 outliers among 100 measurements (10.00%) 2 (2.00%) low severe 1 (1.00%) low mild 3 (3.00%) high mild 4 (4.00%) high severe ``` If someone wants to double-check these numbers, I encourage you to do so.
|
Resources being components inserted on themselves should also solve the following behavior currently in main: #[derive(Resource)]
struct A;
#[derive(Resource)]
struct B;
let mut world = World::new();
world.spawn((A, B));
// This is fine
assert!(world.get_resource::<A>().is_some());
// This panics
assert!(world.get_resource::<B>().is_some()); |
…ents-as-entities
| /// Returns the index of the current component. | ||
| // TODO: Track down all uses and improve data structures for performance. |
There was a problem hiding this comment.
FYI I just noticed that two important uses are in Table and SparseSets. Especially the Table one is called multiple times when iterating a Query (the SparseSets one only once) and once for every Query::get (!)
Given this I would definitely include the results of query iteration and get benchmarks.
|
I've spent a great deal of time giving this preliminary consideration, and I don't think this is the direction I want to take Bevy. The conceptual model is too unclear, the edge cases are extremely nasty (complexity and impact), and the degree of coupling between component metadata storage and other elements of the ECS is too high. I'm closing this out to avoid further effort being wasted here. The underlying problems and motivating features (perf regressions, serialization issues, fancier relations) are still desirable, but I would like to fully exhaust alternate paths before reopening this design. |
|
For my part, I've always struggled with the critiques this PR attracted. If the conceptual model is unclear: In what way? The edge cases are nasty: What specific edge cases? Complexity, too, is in the eye of the beholder. To really nail down which approach is more complex, we'd have to look at the eventual API surface and how intuitive the behaviour ends up being. I think I've not been specific enough when proposing this change. I've felt that this was a 'sure thing' and neglected going into enough detail. Still, a lot of the critique has felt quite high-level and abstract, which was frustrating. I hope that in one/two years, when the ECS has more features that can take the edge of the edgecases (non-removable components, entity ranges, removing registered components from the world, and probably some I'm missing), a better programmer/communicator can take another attempt. In the meantime, I hope I haven't poisoned the well of discussion by being this persistent /annoying. |
Objective
There are two primary problems we currently have with resources-as-components (#19731) .
Performance regression #23039
In order to get access to a resource entity, you usually need to go through two lookups:
TypeId -> ComponentId -> Entity. TheComponentId -> Entitylookup can potentially be removed, speeding up resource lookup.World Asset Serialization (previously in
bevy_scene) #22968Currently, one big downside of resources-as-components is that any components on resource entities aren't serialized. This forms a barrier for implementing required components for resources. Implementing this correctly is tricky. One of the problems we run into is that
IsResource(ComponentId)cannot be directly copied over becauseComponentIds are not consistent between worlds.Solution
We change
ComponentId(usize)intoComponentId(Entity)and use theEntityAllocatorto createComponentIds. Next, we store all resources on theComponentIdentity. This makes theComponentId -> Entitylookup a no-op. And we can also use theMapEntitiesmachinery to serialize and deserialize worlds.Future Work
In the future, we can look at adding parts of
ComponentInfoas a component to the component entities. That sounds confusing, because it is. This would improve the introspection ability of the ECS (being able to query for metadata about the components). This, however, requires quite a bit of weird bootstrapping, which I don't know how to do and is not necessary for this PR.Testing
This is principally a performance PR, and while I will be doing some benchmarking myself, I don't have the hardware. So I'll be asking someone to give it a once-over when it's ready.