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

Add register_components API for ergonomics #1889

Closed
wants to merge 2 commits into from
Closed

Add register_components API for ergonomics #1889

wants to merge 2 commits into from

Conversation

ghost
Copy link

@ghost ghost commented Apr 11, 2021

A suggestion for #1794.
If accepted, fixes #1794.

The linked issue original code was as follows:

world
    .register_component(ComponentDescriptor::new::<CombatState<Peaceful>>(
        StorageType::SparseSet,
    ))
    .unwrap();
world
    .register_component(ComponentDescriptor::new::<CombatState<Chasing>>(
        StorageType::SparseSet,
    ))
    .unwrap();
world
    .register_component(ComponentDescriptor::new::<CombatState<Searching>>(
        StorageType::SparseSet,
    ))
    .unwrap();

With the new API, this is now:

world
    .register_components(vec![
        ComponentDescriptor::new::<CombatState<Peaceful>>(StorageType::SparseSet)),
        ComponentDescriptor::new::<CombatState<Chasing>>(StorageType::SparseSet)),
        ComponentDescriptor::new::<CombatState<Searching>>(StorageType::SparseSet)),
    ]).unwrap();

Or perhaps simpler as done in the added docs+test:

fn cd<T: Component>() -> ComponentDescriptor {
    ComponentDescriptor::new::<T>(StorageType::SparseSet)
 }

let descriptors = vec![
    cd::<CombatState<Peaceful>>(),
    cd::<CombatState<Chasing>>(),
    cd::<CombatState<Searching>>(),
];
world.register_components(descriptors).unwrap();

Added to both `World` and `AppBuilder`.
Server as a convenience when intending to e.g. add
many sparse set storage components in one go.

Signed-off-by: Torstein Grindvik <torstein.grindvik@gmail.com>
@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Usability A simple quality-of-life change that makes Bevy easier to use labels Apr 11, 2021
Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

Registering components is a nuisance, but seems unavoidable here. For now, I think this is a nice ergonomics improvement with no downside.

where
I: IntoIterator<Item = ComponentDescriptor>,
{
let mut ids = vec![];
Copy link
Contributor

@bjorn3 bjorn3 Apr 11, 2021

Choose a reason for hiding this comment

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

Can you try to pre-allocate this vec based in the iterator size_hint? Or maybe replace the function body with descriptors.map(|descriptor| self.register_component(descriptor)).collect()? That shouls work because of the FromIterator<Result<T, E>> for Result<Vec<T>, E> impl.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, I agree with the use of collect() as it will allow the compiler to optimize this a little better.

Copy link
Author

@ghost ghost Apr 12, 2021

Choose a reason for hiding this comment

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

Pushed the changes from the second suggestion.

Btw. if one were to use size_hint(), would it have been appropriate to unwrap the upper bound and use that?
Seems the upper bound can be None is some cases ("infinite" iterators, or user defined iters) but those seem nonsensical for this API (just curious).

Copy link
Member

Choose a reason for hiding this comment

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

would it have been appropriate to unwrap the upper bound and use that?

Probably not, I could want to use my own iterator that doesn't implement size_hint well and that shouldn't crash

@bjorn3 bjorn3 added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Apr 12, 2021
@cart
Copy link
Member

cart commented Apr 15, 2021

Obligatory pushback on apis that add a new way to do the same thing: do we need this? I agree that the World api with unwrapping is a bit boilerplatey, but most users won't be using World directly.

When comparing the two options for AppBuilder (which the majority of people will use), I don't see a significant win:

// individual
app
    .register_component(ComponentDescriptor::new::<A>(StorageType::SparseSet))
    .register_component(ComponentDescriptor::new::<B>(StorageType::SparseSet))
    .register_component(ComponentDescriptor::new::<C>(StorageType::SparseSet))
// batch
app
    .register_components(vec![
        ComponentDescriptor::new::<A>(StorageType::SparseSet),
        ComponentDescriptor::new::<B>(StorageType::SparseSet),
        ComponentDescriptor::new::<C>(StorageType::SparseSet),
    ])

In general I want to push for "one way" to do things. With two apis, users will need to ask themselves "should I use a batch api here". Theres also the (very minor) issue of the additional allocations the batch api adds for the vec.

@alice-i-cecile
Copy link
Member

When comparing the two options for AppBuilder (which the majority of people will use), I don't see a significant win

This is a more relevant comparison, and I see your point here. Ultimately I would love to have this defined at the type-level somehow (see #1843), which would help reduce the boilerplate substantially and encourage much more local code organization.

@ghost ghost closed this Apr 15, 2021
@ghost
Copy link
Author

ghost commented Apr 15, 2021

Obligatory pushback on apis that add a new way to do the same thing: do we need this? I agree that the World api with unwrapping is a bit boilerplatey, but most users won't be using World directly.

When comparing the two options for AppBuilder (which the majority of people will use), I don't see a significant win:

// individual
app
    .register_component(ComponentDescriptor::new::<A>(StorageType::SparseSet))
    .register_component(ComponentDescriptor::new::<B>(StorageType::SparseSet))
    .register_component(ComponentDescriptor::new::<C>(StorageType::SparseSet))
// batch
app
    .register_components(vec![
        ComponentDescriptor::new::<A>(StorageType::SparseSet),
        ComponentDescriptor::new::<B>(StorageType::SparseSet),
        ComponentDescriptor::new::<C>(StorageType::SparseSet),
    ])

In general I want to push for "one way" to do things. With two apis, users will need to ask themselves "should I use a batch api here". Theres also the (very minor) issue of the additional allocations the batch api adds for the vec.

I agree, this comparison shows that the added cognitive load of more APIs is likely not worth.
Closed.

This pull request was closed.
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-Usability A simple quality-of-life change that makes Bevy easier to use S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

An easier way to register multiple components' storage at once
6 participants