-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
Aggressively use let-else and combinators to reduce indentation #7104
Conversation
Tentatively marking this as controversial as it's a stylistic change with a lot of churn. |
I think this should wait for rust-lang/rustfmt#4914 |
Yeah, I really like let-else, but the fact that rustfmt doesn't work is a bit annoying right now. Fortunately the tracking issue for the style guide was closed yesterday so hopefully it doesn't block for too long. |
If it does block for too long, I may end up closing this and just remaking the PR from scratch, as this level of change is really prone to merge conflicts. |
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 mostly looks good to me, and I would approve, although I'd be lying if i said my eyes didnt glaze over for the second half of this PR. Its a lot of code but I did not spot anything out of the ordinary or obviously wrong.
.unwrap_or_else(|_| { | ||
env::current_exe() | ||
.map(|path| { | ||
path.parent() | ||
.map(|exe_parent_path| exe_parent_path.to_owned()) | ||
.unwrap() | ||
}) | ||
.unwrap() | ||
}) |
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.
.unwrap_or_else(|_| { | |
env::current_exe() | |
.map(|path| { | |
path.parent() | |
.map(|exe_parent_path| exe_parent_path.to_owned()) | |
.unwrap() | |
}) | |
.unwrap() | |
}) | |
.or_else(|| { | |
env::current_exe() | |
.and_then(|path| path.parent()) | |
.and_then(|parent_path| parent_path.to_owned()) | |
}).unwrap() |
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.
unwrap_or_else does not ever panic, unless you panic in the closure yourself. Neither will or_else into unwrap, but it will create unreachable code for panicking that may not get optimized out under certain configurations.
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.
or_else into unwrap can panic if the closure returns None
let asset = asset.downcast::<T>().unwrap_or_else(|_| { | ||
panic!( | ||
"Failed to downcast asset to {}.", | ||
std::any::type_name::<T>() | ||
); | ||
} | ||
) | ||
}); |
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.
let Some(asset) = asset.downcast::<T>() else {
panic!(
"Failed to downcast asset to {}.",
std::any::type_name::<T>()
)
};
or even
let asset = asset.downcast::<T>().expect(
format!(
"Failed to downcast asset to {}.",
std::any::type_name::<T>()
)
);
but the latter may have performance impact of formatting even when we're not going to panic
.unwrap_or_else(|| { | ||
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World.", std::any::type_name::<T>(), self.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.
i would suggest expect(format!())
style here but there may be a runtime cost of formatting every time regardless of whether we're going to panic or not
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 perf impact is precisely why it's using unwrap_or_else here. We shouldn't be allocating when we aren't panicking.
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.
Maybe breaking that panic message and args up into multiple lines, it's a bit much.
Yeah this is going to conflict fast... |
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.
Take these with a grain of salt, and excuse me if my suggestions of map_or_else
are incorrect.
} | ||
let Some(mut children) = world.get_mut::<Children>(entity) else { return }; | ||
for e in std::mem::take(&mut children.0) { | ||
despawn_with_children_recursive_inner(world, e); |
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.
How about:
/// Function for despawning an entity and all its children
pub fn despawn_with_children_recursive(world: &mut World, entity: Entity) {
// first, make the entity's own parent forget about it
let Some(parent) = world.get::<Parent>(entity).map(|parent| parent.0) else { return };
let Some(mut children) = world.get_mut::<Children>(parent) else { return };
children.0.retain(|c| *c != entity);
// then despawn the entity and all of its children
despawn_with_children_recursive_inner(world, entity);
}
// Should only be called by `despawn_with_children_recursive`!
fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity) {
despawn_children(world, entity);
if !world.despawn(entity) {
debug!("Failed to despawn entity {:?}", entity);
}
}
fn despawn_children(world: &mut World, entity: Entity) {
let Some(mut children) = world.get_mut::<Children>(entity) else { return };
for e in std::mem::take(&mut children.0) {
despawn_with_children_recursive_inner(world, e);
}
}
@@ -739,11 +739,10 @@ impl World { | |||
/// Returns an iterator of entities that had components of type `T` removed | |||
/// since the last call to [`World::clear_trackers`]. | |||
pub fn removed<T: Component>(&self) -> std::iter::Cloned<std::slice::Iter<'_, 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.
self.components
.get_id(TypeId::of::<T>())
.map_or_else(|| [].iter().cloned(), |component_id| self.removed_with_id(component_id))
Would this work?
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.
seems map_or_else could be used in a bunch of 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.
map_or_else
is so ugly with the else first...
fn contains_component_with_type(world: &World, type_id: TypeId, location: EntityLocation) -> bool { | ||
if let Some(component_id) = world.components.get_id(type_id) { | ||
contains_component_with_id(world, component_id, location) | ||
} else { | ||
false | ||
} | ||
world | ||
.components | ||
.get_id(type_id) | ||
.map(|component_id| contains_component_with_id(world, component_id, location)) | ||
.unwrap_or(false) | ||
} |
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.
world
.components
.get_id(type_id)
.map_or_else(|| false, |component_id| contains_component_with_id(world, component_id, location))
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.
At least for bool
, use .map_or(false, ...)
; it should be slightly less work for the compiler and it reads a bit better.
.unwrap_or_else(|| { | ||
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {:?} because it doesn't exist in this World.", std::any::type_name::<T>(), self.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.
Maybe breaking that panic message and args up into multiple lines, it's a bit much.
); | ||
return; | ||
} | ||
let Some(value) = diagnostic.smoothed() else { return }; |
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.
fn log_diagnostic(diagnostic: &Diagnostic) {
let Some(value) = diagnostic.smoothed() else { return };
if diagnostic.get_max_history_length() > 1 {
let Some(average) = diagnostic.average() else { return };
info!(
target: "diagnostics",
// Suffix is only used for 's' or 'ms' currently,
// so we reserve two columns for it; however,
// Do not reserve columns for the suffix in the average
// The ) hugging the value is more aesthetically pleasing
"{name:<name_width$}: {value:>11.6}{suffix:2} (avg {average:>.6}{suffix:})",
name = diagnostic.name,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
} else {
info!(
target: "diagnostics",
"{name:<name_width$}: {value:>.6}{suffix:}",
name = diagnostic.name,
suffix = diagnostic.suffix,
name_width = crate::MAX_DIAGNOSTIC_NAME_WIDTH,
);
}
}
Maybe?
if let Some(oldest) = self.history.front() { | ||
return Some(newest.time.duration_since(oldest.time)); | ||
} | ||
if let (Some(newest), Some(oldest)) = (self.history.back(), self.history.front()) { |
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.
pub fn duration(&self) -> Option<Duration> {
if self.history.len() < 2 {
return None;
}
if let (Some(newest), Some(oldest)) = (self.history.back(), self.history.front()) {
Some(newest.time.duration_since(oldest.time))
} else {
None
}
}
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 bit cleaner IMO, no big deal either way.
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 think the last expression can be replaced with
self.history.back().zip(self.history.front()).map(|(newest, oldest)| newest.time.duration_since(oldest.time))
The wonders (to some, horrors) of monadic composition!
I think the whole function body can be replaced with that if it's fine to have Some(Duration(0.0))
(if self.history.len()
is 1) (.filter(...)
?!)
Closing in favor of the fresher #8358. |
Objective
Improve the code quality of Bevy. The heavy use of
if let X = ...
results in a relentless push to the right as we nest more and more layers inward. This PR aims to reduce that.Solution
Use
let-else
overif let X = ...
where possible to reduce heavy indentation, as well as useOption
andResult
combinators.This PR seemingly has a huge change, but most of these are indentation changes and no logic in the code has been changed.