Entity component access speedup: component address cache
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 22h
- Merged PRs (30d)
- 161
Description
Resuming entity component access thread started https://github.com/bevyengine/bevy/issues/2495.
### Previously discussed ###
`query.get(entity)` 10(worse) to 3(best) times slower then accessing something like `Vec>`.
Motivation is to have ordered container of entites, like `Vec`. Even if data-based `tagging` would be implemented in bevy - that is not always solution.
### Idea ###
Have something like `ComponentIndex`, to
```rust
struct ComponentIndex(usize);
/// Contains pointers to components in ALL archetypes.
/// index is ComponentIndex.
/// Pointer always valid or null.
/// Grow only. Never shrinks. Individual Arc is alternative to this.
struct ComponentTable(
Vec<(mut* u8 /*component ptr*/, TypeId /*for safety check*/)>
);
CachedEntity{
entity: Entity,
component_indices: [ComponentIndex, ComponentIndex, ComponentIndex]
}
Vec>
```
access:
```rust
/// Costs approx. as world.get_entity(*entity).get_component()
let component_index = world.get_component_index(entity, component_id);
/// Virtually world.component_table[component_index].0 as *C1
let component: Optional<&C1> = world.get_component::(component_index);
```
from system (ergonomically-wise this could be improved):
```rust
struct Inventory{
items: Vec>
}
fn test_system(
inventories: Query<&Inventory>,
components_c1: Components<&C1>, // locks all C1 containing archs
components_c2: Components<&C2>, // ... C2 ...
...
){
....
for inventory in inventories.iter(){
for item in inventory.items{
let c1_component_index = item.component_index(0);
let c1= components_c1.get(c1_component_index);
}
}
}
```
Each time `bevy_ecs` move entity component, it should go to associated `ComponentIndex` and update component pointer in the table (O(1) operation). Since ecs structure updated in isolated moment of time - additional thread synchronization looks unnecessary.
Access becomes significantly faster (in compare to original solution) - close to `Box` with a fair wind....
## Caveats
_From experimental implementation._
If entity loose component - ComponentIndex became null, but if entity get component of the same type again - ComponentIndex still remain null, because new ComponentIndex will be created for attached component. In such case, if `get_component` return null, we should try update it with `get_component_index`, or get component as `world.get_entity().get_component()`.
_May be there is a way to efficiently found and update hanging ComponentIndex in the table._
## Disadvantages
Each entity component consume additional 2 `usize`/`ptrsize`s. One for ComponentTable pointer. Another for `ComponentIndex` inside bevy table column (component container) to quickly update pointer in `ComponentTable`.
I would call component pointer update performance-wise insignificant. But if that turns out not to be the case - we could add component attribute, to disable component indexing for it, reducing ComponentTable too.
## Further development
`ComponentIndex` can be replaced with something like `ComponentPtr = **Component` - this could speed up things even further.
Contributor guide
Assessment
This issue has not been assessed yet.