Skip to content
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

Name component simplification #17582

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open

Conversation

eckz
Copy link
Contributor

@eckz eckz commented Jan 28, 2025

Objective

  • While checking how Name component was built, it catch my attention how needlessly complex it was.
  • It's storing a pre-computed hash internally, and it's only used for PartialEq for the case where both hashes are different, as it's assumed that both are Names are different.
    • This is what was bringing most complexity to the component since it forced custom trait implementations instead of directly using #[derive]

Solution

  • Removing custom Debug, Hash, PartialOrd, PartialEq, Serialize and Deserialize. Substituting them with #[derive]
  • Simplify set() and mutate::<F: FnOnce(&mut String)>() with a simpler to_mut()

Testing

  • Did you test these changes? If so, how?
    • cargo run -p ci Locally

Perfomance testing:

I was curious if the performance would suffer or improve compared to the previous implementation, so I've created some benchmarks to compare the PartialEq implementations (since it was the only place where the internal hash was used).
It also tests how it would behave if a lot of Name's are inserted in a HashSet.

The benchmarks are not included in this PR, since I don't see really any value in benchmarking default PartialEq in the future.

The results are the following:

Screenshot 2025-01-28 at 11 19 00

Which kind of shows that previous implementation is only better if somehow you create a ton of small Name's, and compare them where internal strings are relatively small.

Just to be clear, I don't think any of this benchmarks represents any real usage of the Name component, making the previous complex implementation harder to justify.


Migration Guide

  • Name's mutate and set are replaced with a into_mut() that returns a mutable String.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change X-Contentious There are nontrivial implications that should be thought through D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward A-Animation Make things move and change over time A-glTF Related to the glTF 3D scene/model format labels Jan 28, 2025
@chescock
Copy link
Contributor

Since Name derives Reflect, this will change the serialized format of scenes with names. Do we need a Migration Guide to mention that?

There's a serialized Name in load_scene_example.scn.ron that might need to be updated, but I haven't checked how it's used.

"bevy_ecs::name::Name": (

@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 28, 2025
@alice-i-cecile
Copy link
Member

Since Name derives Reflect, this will change the serialized format of scenes with names. Do we need a Migration Guide to mention that?

I would like to ensure that we don't change the serialized format: this is critical enough that a manual impl is worth it. Swapping over to S-Waiting-For-Author.

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide labels Jan 28, 2025
@eckz
Copy link
Contributor Author

eckz commented Jan 28, 2025

Interestingly enough, in current main, the scene example is failing.

Screenshot 2025-01-28 at 20 00 34

I was surprised because the #derive(Serialize, Deserialize) should keep the previous manual implementation by using #[serde(transparent)], but the issue is that nobody updated load_scene_example.scn.ron.

I've updated load_scene_example.scn.ron with the new format (that this PR maintains).

@chescock
Copy link
Contributor

Oh, sorry for the confusion! I forgot which things use Reflect and which use Serialize, and that file threw me off.

@chescock chescock added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels Jan 28, 2025
@mockersf
Copy link
Member

mockersf commented Feb 2, 2025

the current Name was added in #1109 and it's main goal was to use a hashed value instead of the string

and not sure your benches are entirely good, at least for the equals/different variants, as they create the hashes in the bench. I think this should be part of the setup

@alice-i-cecile alice-i-cecile added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged S-Needs-Benchmarking This set of changes needs performance benchmarking to double-check that they help and removed S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it labels Feb 2, 2025
@eckz
Copy link
Contributor Author

eckz commented Feb 3, 2025

I might be doing something wrong here, but I've tried to rewrite the benchmarks to not take in account the initial Name::new, and I came with these results

Screenshot 2025-02-03 at 14 05 51

For reference my machine is
SystemInfo { os: "macOS 14.7 Sonoma", kernel: "23.6.0", cpu: "Apple M1 Pro", core_count: "10", memory: "32.0 GiB" }

Benchmarks are the following:

fn name_eq(c: &mut Criterion) {
    let mut group = c.benchmark_group("name_eq");
    group.warm_up_time(core::time::Duration::from_millis(500));
    group.measurement_time(core::time::Duration::from_secs(5));

    group.bench_function("short-different", |b| {
        let names: Vec<_> = (0..65536).map(|i| Name::new(format!("Component {i}"))).collect();
        b.iter(
            move || {
                let found = black_box({
                    let search_name = Name::new(String::from("Component that does not exist"));
                    names.iter().any(|n| black_box(n == &search_name))
                });
                assert!(!found);
            },
        )
    });

    group.bench_function("short-equals", |b| {
        let name: String = rand::thread_rng()
            .sample_iter(&rand::distributions::Alphanumeric)
            .take(12)
            .map(char::from)
            .collect();

        let names: Vec<_> = (0..65536).map(|_| Name::new(name.clone())).collect();

        b.iter(
            move || {
                let all_same = black_box({
                    let search_name = Name::new(name.clone());
                    names.iter().all(|n| black_box(n == &search_name))
                });
                assert!(all_same);
            },
        )
    });

    group.bench_function("long-different", |b| {
        let suffix: String = rand::thread_rng()
            .sample_iter(&rand::distributions::Alphanumeric)
            .take(512)
            .map(char::from)
            .collect();

        let names: Vec<_> = (0..65536).map(|i| Name::new(format!("Component {i} {suffix}"))).collect();

        b.iter(
            move || {
                let found = black_box({
                    let search_name = Name::new(String::from("Component that does not exist"));
                    names.iter().any(|n| black_box(n == &search_name))
                });
                assert!(!found);
            },
        )
    });

    group.bench_function("long-equals", |b| {
        let name: String = rand::thread_rng()
            .sample_iter(&rand::distributions::Alphanumeric)
            .take(1024)
            .map(char::from)
            .collect();

        let names: Vec<_> = (0..65536).map(|_| Name::new(name.clone())).collect();

        b.iter(
            move || {
                let all_same = black_box({
                    let search_name = Name::new(name.clone());
                    names.iter().all(|n| black_box(n == &search_name))
                });
                assert!(all_same);
            },
        )
    });

    group.finish();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Animation Make things move and change over time A-ECS Entities, components, systems, and events A-glTF Related to the glTF 3D scene/model format C-Code-Quality A section of code that is hard to understand or change D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Benchmarking This set of changes needs performance benchmarking to double-check that they help S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants