-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Mutable access control for components #1635
Comments
I don't think Bevy needs to provide a built-in "solution" for basic encapsulation. To clarify: this is not a Bevy-specific problem that needs to be addressed in a Bevy-specific way. |
Is there an existing tool for this in Rust that you would recommend here, or should we be asking about this upstream? |
What exactly are you trying to prevent, systems using mutable borrows when the user wants to use mutable borrows? This is not a footgun, an emergent antipattern, or a caveat: it's a user error. It's not our fault if the user breaks invariants they themselves wrote, and it's not our job to design their APIs for them. To answer the question: it's encapsulation. In Rust it's done by using the "Unfortunately, this restricts both immutable and mutable access in the same way, preventing us from reading the component elsewhere." from the leading post is just embarassignly wrong: pub struct MyComponent(usize);
impl MyComponent {
// This can be used by anything that can access `MyComponent`.
pub fn get(&self) -> usize {
self.0
}
// This can be used by anything in the same crate.
pub(crate) fn increment(&mut self) {
*self.0 += 1;
}
// This can be used by anything in the parent module.
pub(super) fn decrement(&mut self) {
*self.0 -= 1;
}
// This can be used only in the same module.
fn set(&mut self, data: usize) {
*self.0 = data;
}
} |
This was relayed from I agree with this explanation; I'll link this and close the issue. |
@Ratysz here's an example I was thinking of: Say I have a crate /// Position on the grid
struct Position(x,y);
/// Cache store for fast position -> entities lookup
struct Entities {
positions: HashMap<Position, HashSet<Entitiy>>,
entities: HashMap<Entity, Position>
}
/// Listen for position changes, update the `Entities` cache.
fn update_entity_position(
query: Query<(Entity, Position), Changed<Position>>,
mut entities: ResMut<Entities>,
) { /* ... */ }
/// Plugin that registers the relevant resources/systems
impl Plugin for GridPlugin { /* ... */ } Now, in a second crate
So far, all of this works as expected. However, things break down when someone accidentally starts mutating the Now, you are correct that I can just not expose mutable access to the inner hash maps outside the grid crate through I get that this could (should?) be considered an edge-case that Bevy shouldn't protect against, but this was mostly me musing on Discord on if/how it would look like if there was a way to "lock" certain resources (or components) in place, providing immutable access to whoever wants to use them, but guaranteeing that they stay in place, and that certain invariants are upheld. As I'm typing this, I realized that one potential "solution" to this would be to have some kind of internal Also, as I'm typing the above, I guess (but I haven't tested this yet), that I can just not implement Although of course, that still allows someone to remove the resource, breaking the invariants. This is even more of a niche case though, and probably warrants a "throw hands up, panic and abort" response from the grid plugin, as it can no longer function properly. So yeah, I believe that "use Rust-provided encapsulation" gets you quite far, but doesn't provide 100% invariant control, as people can still remove initialized resources/components when you don't want that happening. It appears I am mostly looking for a "disallow this resource/component from ever being removed (from this entity)" feature. But this is probably too much of a niche concern to program against in Bevy. |
Archetype invariants (#1481) and kinded entities (#1634) will give you this eventually, presuming that we can get a working implementation :) I suppose, since #1525 made resources stored as components we actually get resource invariants like this nearly for free too, fully solving your use case... |
I mean, if your users are actively antagonistic against your plugin, should it really try to continue providing them with its functions? You have a guard rail and a safety net, if someone polevaults over all of that in an effort to hurt themselves then so be it. |
What problem does this solve or what need does it fill?
Right now, if you want to guarantee true type-level invariants on some of your data, you have to stuff it all together in a container type, and then provide public functions that do the relevant mutation while upholding the required invariants.
Splitting up such a monolithic container into smaller types allows more concurrent read access to the data, but also risks mis-using mutable access for these types.
-@JeanMertz on Discord
What solution would you like?
@TheRawMeatball thinks this could be done with derives for components, allowing you to export immutable-only variants of components for use elsewhere in your game.
What alternative(s) have you considered?
Use
pub
to control mutable access. Unfortunately, this restricts both immutable and mutable access in the same way, preventing us from reading the component elsewhere. This is an important distinction, since read-only access is much less likely to create horrifying spaghetti code.Additional context
None.
The text was updated successfully, but these errors were encountered: