-
-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Document and lock down bevy_ecs::component
APIs
#6750
Conversation
@@ -231,12 +271,13 @@ pub struct ComponentId(usize); | |||
|
|||
impl ComponentId { | |||
#[inline] | |||
pub const fn new(index: usize) -> ComponentId { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to be able to round-trip ComponentId
through some sort of shareable identifier.
Is there a reason for disallowing this?
Maybe we can have ComponentId::from_bits
/ComponentId::into_bits
(or s/bits/raw or similiar) to clearly document that they can only be created from each other and aren't stable across runs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The primary one is to avoid users creating their own ComponentId
s arbitrarily and converting them into something that might be saved for the exact same reasons you've enumerated: it's not stable across runs and shouldn't be easy to convert to something where you can easily manipulate the representation and reconstruct a valid ComponentId out of it, nor allow users to serialize it to a file or over the network.
Is the use case here to communicate this across runtime boundaries (i.e. scripting)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we need to be able to pass component IDs to and from scripts to query components from an entity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the point is to cross a potential FFI boundary for scripting, we can set it to repr(transparent)
if need be, is that sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
repr(transparent)
seems to be strictly worse to me because it's way less obvious to use (I think you have to transmute?) and it gives you no obvious place to list the limitations of round tripping through usize.
} | ||
|
||
#[inline] | ||
pub fn init_resource<T: Resource>(&mut self) -> ComponentId { | ||
pub(crate) fn init_resource<T: Resource>(&mut self) -> ComponentId { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be left public, or be replaced with init_resource_with_descriptor
.
I see no harm in having
Components::init_component_with_descriptor
required for dynamic typesComponents::init_resource_with_descriptor
required for dynamic typesComponents::init_component<T>
Components::init_resource<T>
The latter two are useful even externally, because it is impossible to useinsert_resource_by_id
with a non-initialized resource.
So if you have a resource that isn't automatically inserted, it would be impossible to insert that resource from a script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually init_component_with_descriptor
and init_resource_with_descriptor
do the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to have these methods not on Components
but on World
, then we need to expose all of them. Currently, only init_component
exists, but it has only internal usages. Also I don't like the name on World
, because init_resource
means "insert the default value", so init_component
should not sound as similar as it does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is going to be tough. If we want to do this on World
, we also want to make storages_mut
, which is a giant hole for unsoundness if left open.
We could make ComponentsMut<'a>
wrap a &'a mut ComponentsInternal
and &'a mut Storages
, but that might be a bit overkill for just this.
@jakobhellermann's comments are excellent: I'm in agreement with all of those :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More documentation feedback :)
Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
…to document-ecs-component
#[derive(Debug, Default)] | ||
/// The metadata store for all components within a [`World`]. | ||
/// | ||
/// A public read-only API is accessbile. To initialize a new component, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// A public read-only API is accessbile. To initialize a new component, | |
/// A public read-only API is accessible. To initialize a new component, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a little confused by what you mean here. This sentence implies the existence of a public read-only API, but I'm not sure how that relates to the components struct.
/// | ||
/// A public read-only API is accessbile. To initialize a new component, | ||
/// use [`World::init_component`], [`World::init_resource`], [`World::init_non_send_resource`] or | ||
/// [`World::init_component_with_descriptor`]. Initialized components cannot be removed or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ What is the difference between an initialized vs uninitialized component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIRC components have to be assigned a ComponentId
on a per-world basis, which occurs during initialization.
Would be good to expland on here though.
indices: std::collections::HashMap<TypeId, usize, fxhash::FxBuildHasher>, | ||
resource_indices: std::collections::HashMap<TypeId, usize, fxhash::FxBuildHasher>, | ||
ids: HashMap<TypeId, ComponentId, fxhash::FxBuildHasher>, | ||
resource_ids: HashMap<TypeId, ComponentId, fxhash::FxBuildHasher>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Sorry if this is a super basic question, but I always assumed resources were something totally different from components. But here we are storing resource ids in the components struct, which makes me think they are the same thing.
Are resources just components?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're lacking a good word for components + resources :) They share a lot of the same infrastructure, but the naming uses Component in a few places :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They still have the same IDs, ArchetypeComponentIds, etc. for controlling access and identification, even if the underlying methodology and storage is separated.
@@ -410,34 +493,43 @@ impl Components { | |||
index | |||
} | |||
|
|||
/// Gets the total number of components registered. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ What is a registered component? Is it the same thing as an initialized component?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again, digging into this stuff always fascinates me :)
Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com>
Co-authored-by: Carter Weinberg <weinbergcarter@gmail.com>
I'm unclear of the comments from #6750 (comment) WRT the publicness of |
I get very worried and uneasy whenever I see PRs for "make things no longer You always run the risk of shutting down more exotic usecases that you might not have thought anyone would want to do. You risk causing pain to future users who have a clever idea for something they want to do, but can't, because you made the things they need private. One of Bevy's great strengths is how easy it is for people to hack on, and how so many things can be implemented in 3rd-party crates. Please don't "lock down" Bevy! |
I've stated this on Discord but I'll reiterate it here for better logging of the rationale. I'm in general agreement here about the use of pub APIs, within reason. The primary exception lies with bevy_ecs, where the unsafe APIs are riddled with implementation specific invariants. It's borderline impossible to use them safely outside of the crate, so the few ones we do expose should be well audited, well documented, damn near stable, and well justified as to why it should be public. Making them pub means we must take them into account inside bevy_ecs too, which can combinatorially expand the number of ways both first and third party crates and bevy_ecs itself can cause unsoundness. Even outside of unsafe use of bevy_ecs, it's imperative we stay conscious of the API surface we're exposing. The modularity of the engine demands we expose certain APIs at crate boundaries, but that also means that we can't change it once we hit 1.0. Hyrum's Law states that if it can be observed, someone will depend on it. If we are to make more of the engine public, the interface should be more curated and deter users from footguns, especially when unsafe is involved. |
@james7132 I have agreement in principle with you on these choices, but there's some outstanding controversy. This is in the 0.11 milestone but things seem to have stalled: what would you like to do with this PR? |
Let's move it to 0.12. There's quite a few things that need to be addressed here and I don't think we can rush them out before the 0.11 deadline. |
Closing this out as there are other PRs that got to this beforehand. |
Objective
Document and lock down
bevy_ecs::component
.Addresses #3362 for
bevy_ecs::component
.Solution
Components
pub(crate). Redirect users to the APIs onWorld
instead ofComponents
.Components: Default
implementation. Replace it with a pub(crate)new
function. ForcesComponents
to be initialized in the context of aWorld
.Components::get_resource_id
toresource_id
to make it consistent withComponents::component_id
ComponentId
a fully opaque ID.ComponentDescriptor
constructors private except for the user-facing case for making components for foreign types.ComponentDescriptor
more of a parameter type than a storage type by removing it's accessors. Force users to useComponentInfo
and getting it viaComponents
's fetch APIs.Changelog
Changed:
Components::get_resource_id
has been renamed toComponents::resource_id
.Changed:
ComponentDescriptor::new_with_layout
has replacedComponentDescriptor::new
.Removed:
Components
'sDefault
implementation.Removed:
Components::init_component
,Components::init_resource
,Components::init_non_send
,Components::init_with_component_descriptor
.Removed:
ComponentId::new
andComponentId::index
.Removed:
ComponentDescriptor::new_resource
,ComponentData::type_id
,ComponentDescriptor::name
,ComponentDescriptor::storage_type
.