Skip to content

Conversation

eugineerd
Copy link
Contributor

@eugineerd eugineerd commented May 9, 2025

Objective

This PR allows to fragment archetypes by component value, which is useful for implementing some other ECS features such as:

  • Fragmenting relations
  • Shared components
  • Indexes
  • Maybe more?

Solution

Challenges

To implement fragmentation by component value there are a couple of problems that needed solving:

  • Archetypes are defined as "a group of entities that share the same components: a world only has one archetype for each unique combination of components", which might not necessarily be true if we also fragment by values (depending on implementation)
  • Bundle is a wholly type-level concept. Bundles of components uniquely identify transitions between different archetypes. Fragmenting by value requires taking into account actual value of components, not just their type.
  • Query is a type-level solution - there is no way to describe by which value components it should filter.

This PR addresses all 3 of these problems to varying levels of success.

Archetype

There are multiple ways to achieve fragmentation of archetypes based on component value. One of the most well-know solutions is to encode each value as a different ComponentId. #17608 goes event further and encodes each value as a unique combination of "Fragmenting Marker Components". These solutions are valid and (probably) more correct, however at the time of writing creating large number ComponentIds is problematic in Bevy, although there is ongoing work to fix this. To make this usable before all the issues are fixed, this PR takes a different approach, which doesn't inflate ComponentId and is designed to be replaceable by whatever approach will turn out to be more optimal.

This PR changes archetype's identity to also contain value components:

#[derive(Hash, PartialEq, Eq)]
struct ArchetypeComponents {
    table_components: Box<[ComponentId]>,
    sparse_set_components: Box<[ComponentId]>,
    value_components: FragmentingValues, // NEW
}

Now some component values can become a part of archetype's identity. It also changes Edges to take component values into account when building transition graph between archetypes.

Bundle

Internally every combination of components, Bundle, has a unique BundleId. This id depends only on type-level information, which is the reason for why it is problematic to make it utilize component values. This PR addresses this issue by allowing to create FragmentingValuesBorrowed from bundles of supported components, which can be used to traverse the Edges graph based on component values. Various batched operations also have been modified to make them aware of the fact that some bundles (can be determined at compile time) might need special handling.

pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static {
    // ...

    /// Gets all [`FragmentingValue`]s of this bundle. `values` will be called at most [`Bundle::count_fragmenting_values`] times,
    /// but won't run for unregistered but otherwise valid fragmenting components.
    ///
    /// [`FragmentingValue`]: crate::component::FragmentingValue
    #[doc(hidden)]
    fn get_fragmenting_values<'a>(
        &'a self,
        components: &Components,
        values: &mut impl FnMut(FragmentingValueBorrowed<'a>),
    );

    /// Returns the exact number of [`FragmentingValueComponent`]s in this bundle.
    ///
    /// [`FragmentingValueComponent`]: crate::component::FragmentingValueComponent
    #[doc(hidden)]
    fn count_fragmenting_values() -> usize;
}

Query

Removed from this PR to make reviewing it easier. It is best to implement filtering by component values for queries in a separate PR that'll be more focused on that.

Previous explanation

We cannot express which component values to choose in system's query signature and must be used inside the system body. Since filtering by component value is supposed to be archetype-level filtering, this complicates things, as we have to remove already matched archetypes from the QueryState. To work around this issue, this PR adds filter_by_component_value and filter_by_component_value_and_id to queries, which returns a QueryLens. It's not the most optimal implementation - there is an inherent QueryState cloning overhead, as well as the extra filtering of already matched archetypes. This part of the PR is probably the weakest since it was more of an afterthought - my main reason for implementing fragmentation by value was to be utilized by shared components, which require only archetype fragmentation, not filtering by query. Either way, I think it is best to implement more robust query filtering in a separate PR.

fn system_fragmenting(query: Query<&Fragmenting>) {
    for fragmenting in query.filter_by_component_value(&Fragmenting(1)).query().iter() {
        assert_eq!(fragmenting, &Fragmenting(1));
    }
}

Components

Components that can be used as fragmenting value components must implement the following traits: Component<Mutability = Immutable> + Eq + Hash + Clone

All of these restrictions are checked statically at compile time using a new associated type Component::Key. There are 2 main types of component keys for now: NoKey which is the default component behavior, and Self which is the new fragmenting by value behavior.

#[derive(Component, Clone, Eq, PartialEq, Hash)]
#[component(
    key=Self,
    immutable,
)]
struct Fragmenting(u32); // Each u32 value will be in a different archetype.

This practically ensures that all components within the archetype contain the same value (or however component's PartialEq::eq implementation defines "same") and it seems a bit wasteful to store all of them. This is something I'm planning to solve in a later PR by introducing new storage type - Shared, which would allow to store one component value per archetype instead of one per entity.

Accessing fragmenting values

Each fragmenting value is stored only once in FragmentingValuesStorage (accessible from Storages) when a bundle containing it is first inserted. All archetypes also store references to their fragmenting values and can be accessed using Archetype::get_fragmenting_value_component and Archetype::fragmenting_value_components. This will become important when implementing querying by fragmenting value and Shared components, but for now the only visible effect of this PR is the archetype fragmentation.

Testing

component/fragmenting_value.rs contains some tests covering the added functionality.

Benchmarks
group                                                                                                     main                                     value-components2
-----                                                                                                     ----                                     -----------------
add_remove/sparse_set                                                                                     1.00    991.5±6.55µs        ? ?/sec      1.20  1193.7±10.24µs        ? ?/sec
add_remove/table                                                                                          1.00   1526.8±5.67µs        ? ?/sec      1.04   1587.1±9.43µs        ? ?/sec
add_remove_big/sparse_set                                                                                 1.16  1209.2±13.57µs        ? ?/sec      1.00  1038.1±10.46µs        ? ?/sec
add_remove_big/table                                                                                      1.00      2.9±0.06ms        ? ?/sec      1.00      2.9±0.06ms        ? ?/sec
add_remove_fragmenting_value/fragmenting                                                                                                           1.00      2.6±0.03ms        ? ?/sec
add_remove_fragmenting_value/non_fragmenting                                                                                                       1.00      2.3±0.03ms        ? ?/sec
add_remove_very_big/table                                                                                 1.00     95.5±1.91ms        ? ?/sec      1.00     95.3±0.82ms        ? ?/sec
added_archetypes/archetype_count/100                                                                      1.00     34.9±0.57µs        ? ?/sec      1.02     35.5±0.39µs        ? ?/sec
added_archetypes/archetype_count/1000                                                                     1.02    777.2±4.98µs        ? ?/sec      1.00    762.5±7.17µs        ? ?/sec
added_archetypes/archetype_count/10000                                                                    1.00     15.7±0.53ms        ? ?/sec      1.03     16.1±0.45ms        ? ?/sec
despawn_world/10000_entities                                                                              1.00   628.2±15.50µs        ? ?/sec      1.06   668.1±26.65µs        ? ?/sec
despawn_world/100_entities                                                                                1.00      7.6±0.21µs        ? ?/sec      1.06      8.0±0.20µs        ? ?/sec
despawn_world/1_entities                                                                                  1.00   281.7±18.64ns        ? ?/sec      1.02   286.5±13.38ns        ? ?/sec
despawn_world_recursive/10000_entities                                                                    1.00      2.3±0.04ms        ? ?/sec      1.04      2.4±0.04ms        ? ?/sec
despawn_world_recursive/100_entities                                                                      1.00     25.6±0.51µs        ? ?/sec      1.05     27.0±0.50µs        ? ?/sec
despawn_world_recursive/1_entities                                                                        1.00   563.7±18.47ns        ? ?/sec      1.07   600.4±35.26ns        ? ?/sec
ecs::bundles::insert_many::insert_many/all                                                                1.00      4.5±0.09ms        ? ?/sec      1.02      4.6±0.09ms        ? ?/sec
ecs::bundles::insert_many::insert_many/only_last                                                          1.00    740.9±6.99µs        ? ?/sec      1.02   752.5±15.20µs        ? ?/sec
ecs::bundles::spawn_many::spawn_many/static                                                               1.00    307.1±6.80µs        ? ?/sec      1.07    327.4±4.24µs        ? ?/sec
ecs::bundles::spawn_many_zst::spawn_many_zst/static                                                       1.00    222.0±3.90µs        ? ?/sec      1.04    231.3±5.23µs        ? ?/sec
ecs::bundles::spawn_one_zst::spawn_one_zst/static                                                         1.24    612.0±6.36µs        ? ?/sec      1.00    494.6±3.67µs        ? ?/sec
insert_commands/insert                                                                                    1.00   811.8±48.11µs        ? ?/sec      1.02   828.0±49.36µs        ? ?/sec
insert_commands/insert_batch                                                                              1.03   312.6±17.78µs        ? ?/sec      1.00   302.3±15.10µs        ? ?/sec
insert_fragmenting_value/base                                                                                                                      1.00  1082.8±10.06µs        ? ?/sec
insert_fragmenting_value/high_fragmentation_base                                                                                                   1.00  1297.2±12.66µs        ? ?/sec
insert_fragmenting_value/high_fragmentation_unbatched                                                                                              1.00   1332.6±9.79µs        ? ?/sec
insert_fragmenting_value/unbatched                                                                                                                 1.00  1078.8±14.80µs        ? ?/sec
insert_simple/base                                                                                        1.00    434.5±5.86µs        ? ?/sec      1.07    466.0±2.08µs        ? ?/sec
insert_simple/unbatched                                                                                   1.00    819.1±9.34µs        ? ?/sec      1.12   915.2±15.41µs        ? ?/sec
nonempty_spawn_commands/10000_entities                                                                    1.00    465.2±8.56µs        ? ?/sec      1.01    471.7±9.36µs        ? ?/sec
nonempty_spawn_commands/1000_entities                                                                     1.00     46.5±1.12µs        ? ?/sec      1.00     46.7±1.05µs        ? ?/sec
nonempty_spawn_commands/100_entities                                                                      1.02      4.6±0.09µs        ? ?/sec      1.00      4.6±0.06µs        ? ?/sec
observe/observer_custom                                                                                   1.02    276.5±1.17µs        ? ?/sec      1.00    271.2±1.04µs        ? ?/sec
observe/observer_custom/10000_entity                                                                      1.00  925.5±209.55µs        ? ?/sec      1.02  943.9±209.06µs        ? ?/sec
observe/observer_lifecycle_insert                                                                         1.00    720.3±4.06µs        ? ?/sec      1.04    750.7±3.31µs        ? ?/sec
spawn_commands/10000_entities                                                                             1.00   1520.6±8.50µs        ? ?/sec      1.02  1552.5±11.14µs        ? ?/sec
spawn_commands/1000_entities                                                                              1.00    147.2±2.62µs        ? ?/sec      1.02    150.3±2.71µs        ? ?/sec
spawn_commands/100_entities                                                                               1.00     14.7±0.27µs        ? ?/sec      1.02     15.0±0.27µs        ? ?/sec
spawn_world/10000_entities                                                                                1.00  888.9±205.77µs        ? ?/sec      1.15  1020.4±170.09µs        ? ?/sec
spawn_world/100_entities                                                                                  1.00      7.7±1.71µs        ? ?/sec      1.16      8.9±1.56µs        ? ?/sec
spawn_world/1_entities                                                                                    1.00    78.7±18.27ns        ? ?/sec      1.16    90.9±16.51ns        ? ?/sec

Copy link
Contributor

github-actions bot commented May 9, 2025

Your PR increases Bevy Minimum Supported Rust Version. Please update the rust-version field in the root Cargo.toml file.

@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events X-Controversial There is active debate or serious implications around merging this PR M-Needs-Release-Note Work that should be called out in the blog due to impact labels May 9, 2025
Copy link
Contributor

github-actions bot commented May 9, 2025

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.

@alice-i-cecile alice-i-cecile added M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels May 9, 2025
Copy link
Contributor

github-actions bot commented May 9, 2025

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Please review the instructions for writing migration guides, then expand or revise the content in the migration guides directory to reflect your changes.

@janhohenheim janhohenheim modified the milestone: 0.17 May 11, 2025
Copy link
Contributor

@chescock chescock left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, cool, you got this to work!

I think it is best to implement more robust query filtering in a separate PR.

Would it make sense to just remove querying from this PR entirely, then? The implementation here proves that it's possible to query these, which is important! But removing it from this PR will make it a little easier to review, and should make the future PR to make querying work well easier to review and bikeshed since it will be starting from scratch.

  • Must not have Table storage type (maybe possible to remove this restriction?)

What is the reason for this restriction in the current implementation? I didn't see anything obvious that would fail without the check.

@eugineerd
Copy link
Contributor Author

Would it make sense to just remove querying from this PR entirely, then? The implementation here proves that it's possible to query these, which is important! But removing it from this PR will make it a little easier to review, and should make the future PR to make querying work well easier to review and bikeshed since it will be starting from scratch.

I can do that. There wouldn't be any way to use this new functionality without query implementation, so I though I had to provide at least some way to justify the changes. But in general it is not required for what I want to use this functionality for later.

  • Must not have Table storage type (maybe possible to remove this restriction?)

What is the reason for this restriction in the current implementation? I didn't see anything obvious that would fail without the check.

I think this was because I needed the components to be filterable on set_archetype in WorldQuery? I wanted to do the filtering somewhere in fetch implementation first, but later the plans changed and I probably don't need this anymore. The INVARIANT_ASSERT would still need to exist for the Shared storage type I'm planning on adding next, but maybe that can be done in the PR that would actually add it then?

@chescock
Copy link
Contributor

I can do that. There wouldn't be any way to use this new functionality without query implementation, so I though I had to provide at least some way to justify the changes. But in general it is not required for what I want to use this functionality for later.

I think folks here are usually okay with splitting things up even if the intermediate steps aren't immediately useful, and I think this is well motivated by the future work, but I'm just a random reviewer with no actual authority :). If you leave it in, I probably won't be able to resist bikeshedding it a bit, though :).

I think this was because I needed the components to be filterable on set_archetype in WorldQuery? I wanted to do the filtering somewhere in fetch implementation first, but later the plans changed and I probably don't need this anymore. The INVARIANT_ASSERT would still need to exist for the Shared storage type I'm planning on adding next, but maybe that can be done in the PR that would actually add it then?

I don't have strong opinions here; I was just worried that I missed something. Once Shared exists, I can't imagine there will be a reason to use anything else, so it makes sense that we'll want the checks eventually.

eugineerd added 5 commits May 14, 2025 17:27
It was just example code for this feature and wasn't intended to be usable anyway.
This restriction isn't really required for current implementation.
`cache_archetype_after_bundle_insert` resets `fragmenting_values_map` when caching new edges.
Now this is skipped if base archetype already exists and the only thing changed is the fragmenting value components.
@eugineerd
Copy link
Contributor Author

@alice-i-cecile
Now that I've removed filter_by_component_value implementation for Query, I don't think this needs a release note or new examples anymore? The changes are purely internal now and don't introduce any new user-usable features.

@eugineerd
Copy link
Contributor Author

Figured out how to disable cpu dynamic boost on my laptop and ran some benchmarks:

Benchmarks
group                                                    fragmenting-value                        main-no-boost
-----                                                    -----------------                        -------------
add_remove/sparse_set                                    1.16  1447.3±34.71µs        ? ?/sec      1.00  1243.6±13.78µs        ? ?/sec
add_remove/table                                         1.14   1829.7±6.85µs        ? ?/sec      1.00  1608.3±11.27µs        ? ?/sec
add_remove_big/sparse_set                                1.02  1497.3±61.67µs        ? ?/sec      1.00  1462.5±46.84µs        ? ?/sec
add_remove_big/table                                     1.00      3.1±0.12ms        ? ?/sec      1.03      3.2±0.10ms        ? ?/sec
add_remove_fragmenting_value/fragmenting                 1.00  1253.1±150.66µs        ? ?/sec   
add_remove_fragmenting_value/non_fragmenting             1.00      2.7±0.02ms        ? ?/sec    
add_remove_very_big/table                                1.17     80.0±2.25ms        ? ?/sec      1.00     68.6±0.94ms        ? ?/sec
added_archetypes/archetype_count/100                     1.03     34.1±1.15µs        ? ?/sec      1.00     33.2±1.90µs        ? ?/sec
added_archetypes/archetype_count/1000                    1.00    788.8±7.29µs        ? ?/sec      1.01    793.7±7.57µs        ? ?/sec
added_archetypes/archetype_count/10000                   1.00     15.5±0.45ms        ? ?/sec      1.11     17.2±1.00ms        ? ?/sec
despawn_world/10000_entities                             1.06   683.5±14.03µs        ? ?/sec      1.00   645.6±12.35µs        ? ?/sec
despawn_world/100_entities                               1.05      8.2±0.22µs        ? ?/sec      1.00      7.8±0.16µs        ? ?/sec
despawn_world/1_entities                                 1.00   282.0±24.57ns        ? ?/sec      1.05   297.4±27.52ns        ? ?/sec
despawn_world_recursive/10000_entities                   1.00      3.7±0.04ms        ? ?/sec      1.00      3.7±0.08ms        ? ?/sec
despawn_world_recursive/100_entities                     1.00     37.9±0.45µs        ? ?/sec      1.01     38.1±0.63µs        ? ?/sec
despawn_world_recursive/1_entities                       1.00   728.2±39.53ns        ? ?/sec      1.00   731.4±42.00ns        ? ?/sec
ecs::entity_cloning::hierarchy_many/clone                1.00   306.1±53.63µs 1161.3 KElem/sec    1.11   341.0±62.33µs 1042.4 KElem/sec
ecs::entity_cloning::hierarchy_many/reflect              1.00   675.1±15.20µs 526.6 KElem/sec     1.01   685.0±58.53µs 519.0 KElem/sec
ecs::entity_cloning::hierarchy_tall/clone                1.07    24.3±64.69µs  2.0 MElem/sec      1.00    22.8±65.65µs  2.1 MElem/sec
ecs::entity_cloning::hierarchy_tall/reflect              1.08    28.2±61.31µs 1764.4 KElem/sec    1.00    26.1±56.81µs 1909.8 KElem/sec
ecs::entity_cloning::hierarchy_wide/clone                1.06     13.9±0.66µs  3.5 MElem/sec      1.00     13.1±0.80µs  3.7 MElem/sec
ecs::entity_cloning::hierarchy_wide/reflect              1.13     18.3±0.32µs  2.7 MElem/sec      1.00     16.2±0.32µs  3.0 MElem/sec
ecs::entity_cloning::single/clone                        1.08  915.5±200.13ns 1066.7 KElem/sec    1.00  850.5±175.14ns 1148.3 KElem/sec
ecs::entity_cloning::single/reflect                      1.03  1772.4±143.57ns 551.0 KElem/sec    1.00  1726.5±145.17ns 565.6 KElem/sec
insert_fragmenting_value/base                            1.00  1103.2±10.66µs        ? ?/sec    
insert_fragmenting_value/high_fragmentation_base         1.00  1276.4±13.56µs        ? ?/sec    
insert_fragmenting_value/high_fragmentation_unbatched    1.00  1356.3±15.01µs        ? ?/sec    
insert_fragmenting_value/unbatched                       1.00   1149.3±5.08µs        ? ?/sec    
insert_simple/base                                       1.06    442.5±4.42µs        ? ?/sec      1.00    418.2±2.30µs        ? ?/sec
insert_simple/unbatched                                  1.13   914.0±11.34µs        ? ?/sec      1.00    809.3±8.07µs        ? ?/sec
no_archetypes/system_count/0                             1.00     17.1±0.09ns        ? ?/sec      1.02     17.3±0.10ns        ? ?/sec
no_archetypes/system_count/10                            1.02    161.8±1.45ns        ? ?/sec      1.00    159.1±1.41ns        ? ?/sec
no_archetypes/system_count/100                           1.00  1529.1±10.17ns        ? ?/sec      1.00  1530.3±37.93ns        ? ?/sec
spawn_world/10000_entities                               1.02  1139.8±268.54µs        ? ?/sec     1.00  1120.6±248.69µs        ? ?/sec
spawn_world/100_entities                                 1.02      9.6±2.30µs        ? ?/sec      1.00      9.5±2.21µs        ? ?/sec
spawn_world/1_entities                                   1.04    99.4±23.02ns        ? ?/sec      1.00    95.4±22.78ns        ? ?/sec

@eugineerd eugineerd mentioned this pull request Jun 1, 2025
@eugineerd
Copy link
Contributor Author

I've run the benchmarks again to see if anything changed with the latest main.

ecs benchmarks with >5% threshold
group                                                                                                     main                                     value-components
-----                                                                                                     ----                                     ----------------
add_remove_big/table                                                                                      1.10      3.0±0.11ms        ? ?/sec      1.00      2.8±0.04ms        ? ?/sec
add_remove_very_big/table                                                                                 1.05     96.2±2.24ms        ? ?/sec      1.00     91.6±0.75ms        ? ?/sec
all_added_detection/50000_entities_ecs::change_detection::Sparse                                          1.11     86.9±1.27µs        ? ?/sec      1.00     78.6±0.51µs        ? ?/sec
all_added_detection/5000_entities_ecs::change_detection::Sparse                                           1.09      8.7±0.08µs        ? ?/sec      1.00      8.0±0.11µs        ? ?/sec
all_changed_detection/50000_entities_ecs::change_detection::Sparse                                        1.00     79.5±1.53µs        ? ?/sec      1.24     99.0±2.10µs        ? ?/sec
all_changed_detection/5000_entities_ecs::change_detection::Sparse                                         1.00      8.3±0.34µs        ? ?/sec      1.16      9.6±0.09µs        ? ?/sec
despawn_world/1_entities                                                                                  1.14   302.4±37.66ns        ? ?/sec      1.00   264.7±14.16ns        ? ?/sec
despawn_world_recursive/1_entities                                                                        1.05   598.9±42.75ns        ? ?/sec      1.00   568.4±25.44ns        ? ?/sec
ecs::bundles::insert_many::insert_many/all                                                                1.05      4.7±0.08ms        ? ?/sec      1.00      4.4±0.12ms        ? ?/sec
ecs::bundles::spawn_many::spawn_many/static                                                               1.00    284.7±5.84µs        ? ?/sec      1.08    308.3±5.37µs        ? ?/sec
ecs::entity_cloning::filter/opt_in_all                                                                    1.00    205.2±0.77ns  4.6 MElem/sec      1.06    216.7±1.41ns  4.4 MElem/sec
ecs::entity_cloning::filter/opt_in_all_keep_all                                                           1.00    211.2±1.33ns  4.5 MElem/sec      1.07    225.5±2.06ns  4.2 MElem/sec
ecs::entity_cloning::filter/opt_in_all_keep_all_without_required                                          1.00    175.3±0.87ns  5.4 MElem/sec      1.07    187.0±0.31ns  5.1 MElem/sec
ecs::entity_cloning::filter/opt_in_all_keep_none                                                          1.00    207.2±1.20ns  4.6 MElem/sec      1.07    222.1±3.40ns  4.3 MElem/sec
ecs::entity_cloning::filter/opt_in_all_keep_none_without_required                                         1.00    183.9±1.14ns  5.2 MElem/sec      1.06    195.3±0.81ns  4.9 MElem/sec
ecs::entity_cloning::filter/opt_in_all_without_required                                                   1.00    178.9±1.21ns  5.3 MElem/sec      1.07    190.9±0.48ns  5.0 MElem/sec
ecs::entity_cloning::filter/opt_in_none                                                                   1.00    162.4±1.07ns  5.9 MElem/sec      1.07    173.6±1.64ns  5.5 MElem/sec
ecs::entity_cloning::filter/opt_out_all                                                                   1.00    187.5±1.30ns  5.1 MElem/sec      1.05    197.0±1.59ns  4.8 MElem/sec
ecs::entity_cloning::filter/opt_out_none                                                                  1.00    186.5±0.74ns  5.1 MElem/sec      1.06    198.4±2.44ns  4.8 MElem/sec
ecs::entity_cloning::filter/opt_out_none_keep_all                                                         1.00    171.9±4.03ns  5.5 MElem/sec      1.07    183.8±0.51ns  5.2 MElem/sec
ecs::entity_cloning::filter/opt_out_none_keep_none                                                        1.00    193.6±1.95ns  4.9 MElem/sec      1.10    213.5±1.60ns  4.5 MElem/sec
ecs::entity_cloning::hierarchy_tall/clone                                                                 1.07    24.3±69.51µs 2047.9 KElem/sec    1.00    22.8±43.01µs  2.1 MElem/sec
ecs::entity_cloning::hierarchy_wide/clone                                                                 1.00     14.0±0.68µs  3.5 MElem/sec      1.05     14.7±0.61µs  3.3 MElem/sec
entity_hash/entity_set_build/3162                                                                         1.05     19.6±1.93µs 153.8 MElem/sec     1.00     18.7±0.82µs 161.6 MElem/sec
entity_hash/entity_set_lookup_hit/10000                                                                   1.00     26.3±0.20µs 363.1 MElem/sec     1.05     27.6±0.24µs 345.6 MElem/sec
entity_hash/entity_set_lookup_miss_id/3162                                                                1.09     12.1±0.07µs 249.2 MElem/sec     1.00     11.1±0.06µs 271.9 MElem/sec
events_send/size_4_events_100                                                                             1.00    156.9±0.59ns        ? ?/sec      1.13    177.7±0.50ns        ? ?/sec
events_send/size_4_events_10000                                                                           1.00     18.0±0.07µs        ? ?/sec      1.05     19.0±0.13µs        ? ?/sec
events_send/size_512_events_100                                                                           1.18      3.6±0.02µs        ? ?/sec      1.00      3.0±0.01µs        ? ?/sec
events_send/size_512_events_1000                                                                          1.18     36.6±0.22µs        ? ?/sec      1.00     31.0±0.17µs        ? ?/sec
events_send/size_512_events_10000                                                                         1.21   389.6±21.55µs        ? ?/sec      1.00   322.0±18.08µs        ? ?/sec
few_changed_detection/50000_entities_ecs::change_detection::Sparse                                        1.13    113.2±2.27µs        ? ?/sec      1.00    100.6±0.88µs        ? ?/sec
few_changed_detection/5000_entities_ecs::change_detection::Sparse                                         1.05      5.7±0.29µs        ? ?/sec      1.00      5.4±0.25µs        ? ?/sec
few_changed_detection/5000_entities_ecs::change_detection::Table                                          1.08      6.0±0.13µs        ? ?/sec      1.00      5.5±0.13µs        ? ?/sec
heavy_compute/base                                                                                        1.06   371.2±25.89µs        ? ?/sec      1.00   351.2±18.31µs        ? ?/sec
insert_simple/base                                                                                        1.00    431.3±4.74µs        ? ?/sec      1.10    473.4±2.80µs        ? ?/sec
iter_fragmented(4096)_empty/foreach_table                                                                 1.00      3.2±0.15µs        ? ?/sec      1.07      3.4±0.15µs        ? ?/sec
iter_fragmented/foreach                                                                                   1.10   211.5±38.23ns        ? ?/sec      1.00   191.6±30.56ns        ? ?/sec
iter_fragmented_sparse/base                                                                               1.00      8.1±0.42ns        ? ?/sec      1.06      8.6±0.03ns        ? ?/sec
iter_fragmented_sparse/wide                                                                               1.35     80.0±4.51ns        ? ?/sec      1.00     59.1±1.06ns        ? ?/sec
iter_simple/wide                                                                                          1.06     60.8±0.45µs        ? ?/sec      1.00     57.2±0.25µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_10000_entities_ecs::change_detection::Sparse    1.11  1424.4±140.19µs        ? ?/sec     1.00  1281.4±113.51µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_1000_entities_ecs::change_detection::Sparse     1.32   157.1±13.68µs        ? ?/sec      1.00   118.8±10.11µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_1000_entities_ecs::change_detection::Table      1.15     84.7±6.68µs        ? ?/sec      1.00     73.7±3.09µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_100_entities_ecs::change_detection::Sparse      1.34     18.0±0.96µs        ? ?/sec      1.00     13.4±0.30µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_100_entities_ecs::change_detection::Table       1.10     11.4±0.90µs        ? ?/sec      1.00     10.3±0.34µs        ? ?/sec
multiple_archetypes_none_changed_detection/100_archetypes_10_entities_ecs::change_detection::Sparse       1.12      2.3±0.09µs        ? ?/sec      1.00      2.0±0.10µs        ? ?/sec
multiple_archetypes_none_changed_detection/20_archetypes_10000_entities_ecs::change_detection::Sparse     1.08   297.8±12.96µs        ? ?/sec      1.00    275.4±9.04µs        ? ?/sec
multiple_archetypes_none_changed_detection/20_archetypes_1000_entities_ecs::change_detection::Sparse      1.08     21.6±1.15µs        ? ?/sec      1.00     19.9±0.51µs        ? ?/sec
multiple_archetypes_none_changed_detection/20_archetypes_100_entities_ecs::change_detection::Sparse       1.10      2.7±0.18µs        ? ?/sec      1.00      2.4±0.05µs        ? ?/sec
multiple_archetypes_none_changed_detection/20_archetypes_10_entities_ecs::change_detection::Sparse        1.16   398.3±11.23ns        ? ?/sec      1.00   342.7±15.53ns        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_10000_entities_ecs::change_detection::Sparse      1.07     51.1±2.23µs        ? ?/sec      1.00     48.0±1.01µs        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_10000_entities_ecs::change_detection::Table       1.09     34.2±1.57µs        ? ?/sec      1.00     31.5±0.96µs        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_1000_entities_ecs::change_detection::Sparse       1.07      5.3±0.15µs        ? ?/sec      1.00      5.0±0.12µs        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_100_entities_ecs::change_detection::Sparse        1.05   626.4±29.56ns        ? ?/sec      1.00    594.0±6.47ns        ? ?/sec
multiple_archetypes_none_changed_detection/5_archetypes_10_entities_ecs::change_detection::Sparse         1.05    111.9±7.19ns        ? ?/sec      1.00    106.4±3.86ns        ? ?/sec
none_changed_detection/50000_entities_ecs::change_detection::Sparse                                       1.00     47.9±0.91µs        ? ?/sec      1.06     50.9±1.69µs        ? ?/sec
nonempty_spawn_commands/10000_entities                                                                    1.06  577.1±114.91µs        ? ?/sec      1.00  544.1±114.56µs        ? ?/sec
nonempty_spawn_commands/1000_entities                                                                     1.07     53.5±8.46µs        ? ?/sec      1.00     50.0±8.25µs        ? ?/sec
nonempty_spawn_commands/100_entities                                                                      1.08      4.9±0.08µs        ? ?/sec      1.00      4.5±0.08µs        ? ?/sec
query_get_many_10/50000_calls_sparse                                                                      1.18      6.2±0.29ms        ? ?/sec      1.00      5.2±0.25ms        ? ?/sec
query_get_many_10/50000_calls_table                                                                       1.16      4.5±0.40ms        ? ?/sec      1.00      3.8±0.20ms        ? ?/sec
query_get_many_2/50000_calls_sparse                                                                       1.00   625.4±42.58µs        ? ?/sec      1.09   684.4±40.72µs        ? ?/sec
query_get_many_2/50000_calls_table                                                                        1.00   742.1±54.28µs        ? ?/sec      1.09   809.6±50.24µs        ? ?/sec
sized_commands_512_bytes/10000_commands                                                                   1.08   333.6±42.96µs        ? ?/sec      1.00   308.4±28.35µs        ? ?/sec
world_entity/50000_entities                                                                               1.08    110.1±0.86µs        ? ?/sec      1.00    102.1±0.48µs        ? ?/sec
world_query_for_each/50000_entities_table                                                                 2.02     31.1±0.49µs        ? ?/sec      1.00     15.4±0.02µs        ? ?/sec

Looks like mostly noise, as far as I see.


There are a couple of small things that could be improved:

  • The number of arguments to ComponentDescriptor::new_with_layout is getting a bit too large, and now there are also incompatible combinations (mutable + fragmenting vtable). Maybe it should be replaced with a builder?
  • The naming is somewhat inconsistent: PR uses "fragmenting value" and "value component" somewhat inconsistently, although I don't really like either of those names. "fragmenting value component" is the most descriptive, but is way too long. Suggestions on better names are welcome.
  • ComponentKey feels a bit too generic right now. I had plans to expand it to support "key" components, which would allow to group multiple components under a single defining "key" component (like if we had AssetId "key" component and Mesh, Material, etc. components declare it as the "key", so if AssetId is removed or changed, all components that use it as a key would be removed/changed as well), but now I'm not so sure if it's even a good idea in the first place. In either case, maybe this detail should be hidden for now and replaced with #[component(fragmenting_value)] that implies #[component(immutable, Key=Self)]?
  • I think it might be possible to remove the Clone bound on FragmentingValue if FragmentingValuesOwned are replaced with Arc'ed version, although this would require larger refactors to bundles, which is out of scope. Having Clone on an immutable component will probably make sense most of the time anyway, so that should be fine.

I don't think anything from the above list is blocking though, so I guess this PR should be ready for review?

@eugineerd
Copy link
Contributor Author

I've rewritten most of this PR to simplify implementation:

  • Unified DynamicFragmentingValue and dyn FragmentingValue trait as FragmentingValue
  • Made FragmentingValueVtable more FFI/scripting-friendly
  • Simplified bundle insertion logic
  • Made FragmentingValue store each value only once using an Arc, which also has nice side effect of making it faster to compare values in the eventual query filtering implementation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide M-Needs-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Controversial There is active debate or serious implications around merging this PR
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

4 participants