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

bevy_ecs: add untyped methods for inserting components and bundles #5602

Closed

Conversation

jakobhellermann
Copy link
Contributor

@jakobhellermann jakobhellermann commented Aug 7, 2022

Objective

Solution

  • add EntityMut::{insert_by_id, insert_bundle_by_id}
  • split Bundle into DynamicBundle with get_components and Bundle: DynamicBundle. This allows the BundleInserter machinery to be reused for bundles that can only be written, not read, and have no statically available ComponentIds
  • add Bundles::bundle_ids_dynamic: HashMap<Vec<ComponentId>, BundleId> next to the HashMap<TypeId, BundleId>

Changelog

  • add methods for inserting bundles and components to: world.entity_mut(entity).insert_bundle_by_id/insert_by_id

Unresolved questions

  • can we get rid of some allocations?

    • init_info_dynamic could remove a .clone() of a Vec<ComponentId> when HashMap::raw_entry stabilizes
    • insert_by_id could not reuse insert_bundle_by_id to avoid the Vec overhead, or alternatively insert_bundle_by_id could maybe be changed to not require the Vec for the OwningPtrs
  • currently insert_bundle_by_ids requires takes a Vec<ComponentId> that must be sorted and a Vec<OwningPtr<'_> that must match the component ids. This is because internally we also need the Vec<ComponentId> so if the caller already has it we can just use that.

    • should we just accept a &[(ComponentId, OwningPtr<'_>)] and internally collect that into a sorted vec of component ids?
    • alternatively, should we sort the ComponentId vec in the function so that we don't have such a subtle safety requirement? sorting an already sorted vec is the best case scenario for insertion sort (which is used for std sort for <=20 elements) so it's just a small O(n) cost

@jakobhellermann jakobhellermann added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events A-Modding Supporting infrastructure for player-controlled modifications to games labels Aug 7, 2022
crates/bevy_ecs/src/world/entity_ref.rs Outdated Show resolved Hide resolved
crates/bevy_ecs/src/world/entity_ref.rs Outdated Show resolved Hide resolved
@NathanSWard
Copy link
Contributor

Pining here to see if there is any plans to pursue this PR?
I wanted to pick up #5074 however, it relies on a DynamicBundle like interface (which is what this PR provides).
I can always adopt this PR, at the discretion of @jakobhellermann of course.

@NathanSWard
Copy link
Contributor

ping @jakobhellermann
Any plans to move forward w/ this PR?
I'm more than happy to take it over if that's easier.

@jakobhellermann
Copy link
Contributor Author

ping @jakobhellermann Any plans to move forward w/ this PR? I'm more than happy to take it over if that's easier.

Sorry for not responding earlier. I'm a bit busy with Uni right now but feel free to adopt this PR.

@jakobhellermann jakobhellermann force-pushed the insert-components-by-id branch 3 times, most recently from 2e1fe5a to 199c646 Compare September 20, 2022 17:40
@jakobhellermann
Copy link
Contributor Author

I rebased the PR and adressed the review comments.

It's not quite ready for review yet because I made the DynamicBundle trait safe because I noticed that it had no actual safety requirements anymore and need to properly think about it to make sure that is correct. Also I don't know if Drop logic is correct right now, so I need to look into that as well. I will do both tomorrow probably.

@jakobhellermann jakobhellermann marked this pull request as draft September 20, 2022 18:54
@jakobhellermann jakobhellermann marked this pull request as ready for review September 21, 2022 10:55
@@ -665,6 +672,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
pub struct Bundles {
bundle_infos: Vec<BundleInfo>,
bundle_ids: HashMap<TypeId, BundleId>,
bundle_ids_dynamic: HashMap<Vec<ComponentId>, BundleId>,
Copy link
Contributor

Choose a reason for hiding this comment

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

The Vec<ComponentId> could be reduced to a Box<[ComponentId]> since it's never appending elements, therefore the entire Vec is not needed.

Copy link

Choose a reason for hiding this comment

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

Creating boxed slices will induce a lot of unnecessary reallocations which are far more expensive than the 8 bytes you save of not having a Vec.

Copy link
Contributor

Choose a reason for hiding this comment

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

ahh yes true, forgot the Vec would need to drop any excess storage and possibly reallocate. Not just a simple pointer transfer.

@jakobhellermann
Copy link
Contributor Author

rebased the PR

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.

I've taken a look, and this seems solid from an API side. @DJMcNab, you made a very similar PR for me at one point, can I get your review?

Copy link
Member

@DJMcNab DJMcNab left a comment

Choose a reason for hiding this comment

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

I'm not quite sure on the API design here; allocating vectors every time you need this is unfortunate.

I also think that the ordering things require more careful consideration.

DynamicBundle also needs to be unsafe I believe. Hmm, technically if all the APIs which use it require it to be implemented correctly as part of their contracts, maybe not?

OTOH, I don't mind making this slow that much; people should just write their game code in Rust.

mut component_ids: Vec<ComponentId>,
components: I,
) -> &mut Self {
component_ids.sort();
Copy link
Member

Choose a reason for hiding this comment

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

This is not obvious correct.

Do you not also need to simultaneously sort the components?

@Suficio
Copy link
Contributor

Suficio commented Nov 11, 2022

This PR looks amazing and is super helpful!

I was looking into implementing something similar and would like to throw a suggestion up for discussion,

Would it be possible to move the ComponentId correctness check into init_info_dynamic which could be made safe and public?

The resulting BundleId could be stored by the user and used in fn insert_bundle_by_ids(&mut self, bundle_id, ...).

This would have the added benefit of being able to remove bundle_ids_dynamic, and init_info_dynamic would document that all dynamic queries will be assigned a unique BundleId (this is akin to what happens to "Components::init_component_with_descriptor").

Could rename init_info_dynamic to init_dynamic_bundle.

EDIT: Dumped the changes in jakobhellermann#69

@alice-i-cecile alice-i-cecile added the X-Controversial There is active debate or serious implications around merging this PR label Jan 15, 2023
@alice-i-cecile
Copy link
Member

Controversial because we need to decide between this and #7204 :)

cart pushed a commit that referenced this pull request Mar 21, 2023
…7204)

This MR is a rebased and alternative proposal to
#5602

# Objective

- #4447 implemented untyped
(using component ids instead of generics and TypeId) APIs for
inserting/accessing resources and accessing components, but left
inserting components for another PR (this one)

## Solution

- add `EntityMut::insert_by_id`

- split `Bundle` into `DynamicBundle` with `get_components` and `Bundle:
DynamicBundle`. This allows the `BundleInserter` machinery to be reused
for bundles that can only be written, not read, and have no statically
available `ComponentIds`

- Compared to the original MR this approach exposes unsafe endpoints and
requires the user to manage instantiated `BundleIds`. This is quite easy
for the end user to do and does not incur the performance penalty of
checking whether component input is correctly provided for the
`BundleId`.

- This MR does ensure that constructing `BundleId` itself is safe

---

## Changelog

- add methods for inserting bundles and components to:
`world.entity_mut(entity).insert_by_id`
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 A-Modding Supporting infrastructure for player-controlled modifications to games C-Feature A new feature, making something new possible X-Controversial There is active debate or serious implications around merging this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants