Skip to content

Commit 3b6d019

Browse files
eugineerdurben1680
andauthored
Unify filtering by id in EntityClonerBuilder (#19977)
# Objective #19649 introduced new `*_if_new` and `*_by_bundle_id_*` variations to `EntityClonerBuilder` filtering functionality, which resulted in increase in method permutations - there are now 8 allow variants to support various id types and 2 different insert modes. ## Solution This PR introduces a new trait `FilterableIds` to unify all id types and their `IntoIterator` implementations, which is somewhat similar to `WorldEntityFetch`. It supports `TypeId`, `ComponentId` and `BundleId`, allowing us to reduce the number of `allow` methods to 4: `allow`, `allow_if_new`, `allow_by_ids`, `allow_by_ids_if_new`. The function signature is a bit less readable now, but the docs mention types that can be passed in. ## Testing All existing tests pass, performance is unchanged. --------- Co-authored-by: urben1680 <55257931+urben1680@users.noreply.github.com>
1 parent d80078c commit 3b6d019

File tree

2 files changed

+130
-104
lines changed

2 files changed

+130
-104
lines changed

crates/bevy_ecs/src/entity/clone_entities.rs

Lines changed: 120 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -795,47 +795,34 @@ impl<'w> EntityClonerBuilder<'w, OptOut> {
795795
/// this behavior.
796796
pub fn deny<T: Bundle>(&mut self) -> &mut Self {
797797
let bundle_id = self.world.register_bundle::<T>().id();
798-
self.deny_by_bundle_id(bundle_id)
799-
}
800-
801-
/// Disallows all components of the bundle ID from being cloned.
802-
///
803-
/// If component `A` is denied here and component `B` requires `A`, then `A`
804-
/// is denied as well. See [`Self::without_required_by_components`] to alter
805-
/// this behavior.
806-
pub fn deny_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self {
807-
if let Some(bundle) = self.world.bundles().get(bundle_id) {
808-
let ids = bundle.explicit_components().iter();
809-
for &id in ids {
810-
self.filter.filter_deny(id, self.world);
811-
}
812-
}
813-
self
798+
self.deny_by_ids(bundle_id)
814799
}
815800

816801
/// Extends the list of components that shouldn't be cloned.
802+
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
817803
///
818804
/// If component `A` is denied here and component `B` requires `A`, then `A`
819805
/// is denied as well. See [`Self::without_required_by_components`] to alter
820806
/// this behavior.
821-
pub fn deny_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
822-
for id in ids {
823-
self.filter.filter_deny(id, self.world);
824-
}
825-
self
826-
}
827-
828-
/// Extends the list of components that shouldn't be cloned by type ids.
829-
///
830-
/// If component `A` is denied here and component `B` requires `A`, then `A`
831-
/// is denied as well. See [`Self::without_required_by_components`] to alter
832-
/// this behavior.
833-
pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
834-
for type_id in ids {
835-
if let Some(id) = self.world.components().get_valid_id(type_id) {
836-
self.filter.filter_deny(id, self.world);
807+
pub fn deny_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
808+
ids.filter_ids(&mut |ids| match ids {
809+
FilterableId::Type(type_id) => {
810+
if let Some(id) = self.world.components().get_valid_id(type_id) {
811+
self.filter.filter_deny(id, self.world);
812+
}
837813
}
838-
}
814+
FilterableId::Component(component_id) => {
815+
self.filter.filter_deny(component_id, self.world);
816+
}
817+
FilterableId::Bundle(bundle_id) => {
818+
if let Some(bundle) = self.world.bundles().get(bundle_id) {
819+
let ids = bundle.explicit_components().iter();
820+
for &id in ids {
821+
self.filter.filter_deny(id, self.world);
822+
}
823+
}
824+
}
825+
});
839826
self
840827
}
841828
}
@@ -865,7 +852,7 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
865852
/// to alter this behavior.
866853
pub fn allow<T: Bundle>(&mut self) -> &mut Self {
867854
let bundle_id = self.world.register_bundle::<T>().id();
868-
self.allow_by_bundle_id(bundle_id)
855+
self.allow_by_ids(bundle_id)
869856
}
870857

871858
/// Adds all components of the bundle to the list of components to clone if
@@ -876,94 +863,55 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
876863
/// to alter this behavior.
877864
pub fn allow_if_new<T: Bundle>(&mut self) -> &mut Self {
878865
let bundle_id = self.world.register_bundle::<T>().id();
879-
self.allow_by_bundle_id_if_new(bundle_id)
880-
}
881-
882-
/// Adds all components of the bundle ID to the list of components to clone.
883-
///
884-
/// If component `A` is allowed here and requires component `B`, then `B`
885-
/// is allowed as well. See [`Self::without_required_components`]
886-
/// to alter this behavior.
887-
pub fn allow_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self {
888-
if let Some(bundle) = self.world.bundles().get(bundle_id) {
889-
let ids = bundle.explicit_components().iter();
890-
for &id in ids {
891-
self.filter
892-
.filter_allow(id, self.world, InsertMode::Replace);
893-
}
894-
}
895-
self
896-
}
897-
898-
/// Adds all components of the bundle ID to the list of components to clone
899-
/// if the target does not contain them.
900-
///
901-
/// If component `A` is allowed here and requires component `B`, then `B`
902-
/// is allowed as well. See [`Self::without_required_components`]
903-
/// to alter this behavior.
904-
pub fn allow_by_bundle_id_if_new(&mut self, bundle_id: BundleId) -> &mut Self {
905-
if let Some(bundle) = self.world.bundles().get(bundle_id) {
906-
let ids = bundle.explicit_components().iter();
907-
for &id in ids {
908-
self.filter.filter_allow(id, self.world, InsertMode::Keep);
909-
}
910-
}
911-
self
866+
self.allow_by_ids_if_new(bundle_id)
912867
}
913868

914869
/// Extends the list of components to clone.
870+
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
915871
///
916872
/// If component `A` is allowed here and requires component `B`, then `B`
917873
/// is allowed as well. See [`Self::without_required_components`]
918874
/// to alter this behavior.
919-
pub fn allow_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
920-
for id in ids {
921-
self.filter
922-
.filter_allow(id, self.world, InsertMode::Replace);
923-
}
875+
pub fn allow_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
876+
self.allow_by_ids_inner(ids, InsertMode::Replace);
924877
self
925878
}
926879

927880
/// Extends the list of components to clone if the target does not contain them.
881+
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
928882
///
929883
/// If component `A` is allowed here and requires component `B`, then `B`
930884
/// is allowed as well. See [`Self::without_required_components`]
931885
/// to alter this behavior.
932-
pub fn allow_by_ids_if_new(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
933-
for id in ids {
934-
self.filter.filter_allow(id, self.world, InsertMode::Keep);
935-
}
886+
pub fn allow_by_ids_if_new<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
887+
self.allow_by_ids_inner(ids, InsertMode::Keep);
936888
self
937889
}
938890

939-
/// Extends the list of components to clone using [`TypeId`]s.
940-
///
941-
/// If component `A` is allowed here and requires component `B`, then `B`
942-
/// is allowed as well. See [`Self::without_required_components`]
943-
/// to alter this behavior.
944-
pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
945-
for type_id in ids {
946-
if let Some(id) = self.world.components().get_valid_id(type_id) {
891+
fn allow_by_ids_inner<M: Marker>(
892+
&mut self,
893+
ids: impl FilterableIds<M>,
894+
insert_mode: InsertMode,
895+
) {
896+
ids.filter_ids(&mut |id| match id {
897+
FilterableId::Type(type_id) => {
898+
if let Some(id) = self.world.components().get_valid_id(type_id) {
899+
self.filter.filter_allow(id, self.world, insert_mode);
900+
}
901+
}
902+
FilterableId::Component(component_id) => {
947903
self.filter
948-
.filter_allow(id, self.world, InsertMode::Replace);
904+
.filter_allow(component_id, self.world, insert_mode);
949905
}
950-
}
951-
self
952-
}
953-
954-
/// Extends the list of components to clone using [`TypeId`]s if the target
955-
/// does not contain them.
956-
///
957-
/// If component `A` is allowed here and requires component `B`, then `B`
958-
/// is allowed as well. See [`Self::without_required_components`]
959-
/// to alter this behavior.
960-
pub fn allow_by_type_ids_if_new(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
961-
for type_id in ids {
962-
if let Some(id) = self.world.components().get_valid_id(type_id) {
963-
self.filter.filter_allow(id, self.world, InsertMode::Keep);
906+
FilterableId::Bundle(bundle_id) => {
907+
if let Some(bundle) = self.world.bundles().get(bundle_id) {
908+
let ids = bundle.explicit_components().iter();
909+
for &id in ids {
910+
self.filter.filter_allow(id, self.world, insert_mode);
911+
}
912+
}
964913
}
965-
}
966-
self
914+
});
967915
}
968916
}
969917

@@ -1309,6 +1257,77 @@ impl Required {
13091257
}
13101258
}
13111259

1260+
mod private {
1261+
use super::*;
1262+
1263+
/// Marker trait to allow multiple blanket implementations for [`FilterableIds`].
1264+
pub trait Marker {}
1265+
/// Marker struct for [`FilterableIds`] implementation for single-value types.
1266+
pub struct ScalarType {}
1267+
impl Marker for ScalarType {}
1268+
/// Marker struct for [`FilterableIds`] implementation for [`IntoIterator`] types.
1269+
pub struct VectorType {}
1270+
impl Marker for VectorType {}
1271+
1272+
/// Defines types of ids that [`EntityClonerBuilder`] can filter components by.
1273+
#[derive(From)]
1274+
pub enum FilterableId {
1275+
Type(TypeId),
1276+
Component(ComponentId),
1277+
Bundle(BundleId),
1278+
}
1279+
1280+
impl<'a, T> From<&'a T> for FilterableId
1281+
where
1282+
T: Into<FilterableId> + Copy,
1283+
{
1284+
#[inline]
1285+
fn from(value: &'a T) -> Self {
1286+
(*value).into()
1287+
}
1288+
}
1289+
1290+
/// A trait to allow [`EntityClonerBuilder`] filter by any supported id type and their iterators,
1291+
/// reducing the number of method permutations required for all id types.
1292+
///
1293+
/// The supported id types that can be used to filter components are defined by [`FilterableId`], which allows following types: [`TypeId`], [`ComponentId`] and [`BundleId`].
1294+
///
1295+
/// `M` is a generic marker to allow multiple blanket implementations of this trait.
1296+
/// This works because `FilterableId<M1>` is a different trait from `FilterableId<M2>`, so multiple blanket implementations for different `M` are allowed.
1297+
/// The reason this is required is because supporting `IntoIterator` requires blanket implementation, but that will conflict with implementation for `TypeId`
1298+
/// since `IntoIterator` can technically be implemented for `TypeId` in the future.
1299+
/// Functions like `allow_by_ids` rely on type inference to automatically select proper type for `M` at call site.
1300+
pub trait FilterableIds<M: Marker> {
1301+
/// Takes in a function that processes all types of [`FilterableId`] one-by-one.
1302+
fn filter_ids(self, ids: &mut impl FnMut(FilterableId));
1303+
}
1304+
1305+
impl<I, T> FilterableIds<VectorType> for I
1306+
where
1307+
I: IntoIterator<Item = T>,
1308+
T: Into<FilterableId>,
1309+
{
1310+
#[inline]
1311+
fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) {
1312+
for id in self.into_iter() {
1313+
ids(id.into());
1314+
}
1315+
}
1316+
}
1317+
1318+
impl<T> FilterableIds<ScalarType> for T
1319+
where
1320+
T: Into<FilterableId>,
1321+
{
1322+
#[inline]
1323+
fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) {
1324+
ids(self.into());
1325+
}
1326+
}
1327+
}
1328+
1329+
use private::{FilterableId, FilterableIds, Marker};
1330+
13121331
#[cfg(test)]
13131332
mod tests {
13141333
use super::*;

release-content/migration-guides/entity_cloner_builder_split.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: EntityClonerBuilder Split
3-
pull_requests: [19649]
3+
pull_requests: [19649, 19977]
44
---
55

66
`EntityClonerBuilder` is now generic and has different methods depending on the generic.
@@ -40,15 +40,15 @@ The methods of the two builder types are different to 0.16 and to each other now
4040

4141
## Opt-Out variant
4242

43-
- Still offers variants of the `deny` methods which now also includes one with a `BundleId` argument.
43+
- Still offers variants of the `deny` methods.
4444
- No longer offers `allow` methods, you need to be exact with denying components.
4545
- Offers now the `insert_mode` method to configure if components are cloned if they already exist at the target.
4646
- Required components of denied components are no longer considered. Denying `A`, which requires `B`, does not imply `B` alone would not be useful at the target. So if you do not want to clone `B` too, you need to deny it explicitly. This also means there is no `without_required_components` method anymore as that would be redundant.
4747
- It is now the other way around: Denying `A`, which is required _by_ `C`, will now also deny `C`. This can be bypassed with the new `without_required_by_components` method.
4848

4949
## Opt-In variant
5050

51-
- Still offers variants of the `allow` methods which now also includes one with a `BundleId` argument.
51+
- Still offers variants of the `allow` methods.
5252
- No longer offers `deny` methods, you need to be exact with allowing components.
5353
- Offers now `allow_if_new` method variants that only clone this component if the target does not contain it. If it does, required components of it will also not be cloned, except those that are also required by one that is actually cloned.
5454
- Still offers the `without_required_components` method.
@@ -62,6 +62,13 @@ All other methods `EntityClonerBuilder` had in 0.16 are still available for both
6262
- `clone_behavior` variants
6363
- `linked_cloning`
6464

65+
## Unified id filtering
66+
67+
Previously `EntityClonerBuilder` supported filtering by 2 types of ids: `ComponentId` and `TypeId`, the functions taking in `IntoIterator` for them.
68+
Since now `EntityClonerBuilder` supports filtering by `BundleId` as well, the number of method variations would become a bit too unwieldy.
69+
Instead, all id filtering methods were unified into generic `deny_by_ids/allow_by_ids(_if_new)` methods, which allow to filter components by
70+
`TypeId`, `ComponentId`, `BundleId` and their `IntoIterator` variations.
71+
6572
## Other affected APIs
6673

6774
| 0.16 | 0.17 |

0 commit comments

Comments
 (0)