Skip to content

Commit c2854a2

Browse files
authored
bevy_reflect: Deprecate PartialReflect::clone_value (bevyengine#18284)
# Objective bevyengine#13432 added proper reflection-based cloning. This is a better method than cloning via `clone_value` for reasons detailed in the description of that PR. However, it may not be immediately apparent to users why one should be used over the other, and what the gotchas of `clone_value` are. ## Solution This PR marks `PartialReflect::clone_value` as deprecated, with the deprecation notice pointing users to `PartialReflect::reflect_clone`. However, it also suggests using a new method introduced in this PR: `PartialReflect::to_dynamic`. `PartialReflect::to_dynamic` is essentially a renaming of `PartialReflect::clone_value`. By naming it `to_dynamic`, we make it very obvious that what's returned is a dynamic type. The one caveat to this is that opaque types still use `reflect_clone` as they have no corresponding dynamic type. Along with changing the name, the method is now optional, and comes with a default implementation that calls out to the respective reflection subtrait method. This was done because there was really no reason to require manual implementors provide a method that almost always calls out to a known set of methods. Lastly, to make this default implementation work, this PR also did a similar thing with the `clone_dynamic ` methods on the reflection subtraits. For example, `Struct::clone_dynamic` has been marked deprecated and is superseded by `Struct::to_dynamic_struct`. This was necessary to avoid the "multiple names in scope" issue. ### Open Questions This PR maintains the original signature of `clone_value` on `to_dynamic`. That is, it takes `&self` and returns `Box<dyn PartialReflect>`. However, in order for this to work, it introduces a panic if the value is opaque and doesn't override the default `reflect_clone` implementation. One thing we could do to avoid the panic would be to make the conversion fallible, either returning `Option<Box<dyn PartialReflect>>` or `Result<Box<dyn PartialReflect>, ReflectCloneError>`. This makes using the method a little more involved (i.e. users have to either unwrap or handle the rare possibility of an error), but it would set us up for a world where opaque types don't strictly need to be `Clone`. Right now this bound is sort of implied by the fact that `clone_value` is a required trait method, and the default behavior of the macro is to use `Clone` for opaque types. Alternatively, we could keep the signature but make the method required. This maintains that implied bound where manual implementors must provide some way of cloning the value (or YOLO it and just panic), but also makes the API simpler to use. Finally, we could just leave it with the panic. It's unlikely this would occur in practice since our macro still requires `Clone` for opaque types, and thus this would only ever be an issue if someone were to manually implement `PartialReflect` without a valid `to_dynamic` or `reflect_clone` method. ## Testing You can test locally using the following command: ``` cargo test --package bevy_reflect --all-features ``` --- ## Migration Guide `PartialReflect::clone_value` is being deprecated. Instead, use `PartialReflect::to_dynamic` if wanting to create a new dynamic instance of the reflected value. Alternatively, use `PartialReflect::reflect_clone` to attempt to create a true clone of the underlying value. Similarly, the following methods have been deprecated and should be replaced with these alternatives: - `Array::clone_dynamic` → `Array::to_dynamic_array` - `Enum::clone_dynamic` → `Enum::to_dynamic_enum` - `List::clone_dynamic` → `List::to_dynamic_list` - `Map::clone_dynamic` → `Map::to_dynamic_map` - `Set::clone_dynamic` → `Set::to_dynamic_set` - `Struct::clone_dynamic` → `Struct::to_dynamic_struct` - `Tuple::clone_dynamic` → `Tuple::to_dynamic_tuple` - `TupleStruct::clone_dynamic` → `TupleStruct::to_dynamic_tuple_struct`
1 parent adbb53b commit c2854a2

36 files changed

+408
-385
lines changed

benches/benches/bevy_reflect/list.rs

+10-10
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use criterion::{
1010
criterion_group!(
1111
benches,
1212
concrete_list_apply,
13-
concrete_list_clone_dynamic,
13+
concrete_list_to_dynamic_list,
1414
dynamic_list_apply,
1515
dynamic_list_push
1616
);
@@ -81,20 +81,20 @@ fn concrete_list_apply(criterion: &mut Criterion) {
8181
list_apply(&mut group, "empty_base_concrete_patch", empty_base, patch);
8282

8383
list_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
84-
patch(size).clone_dynamic()
84+
patch(size).to_dynamic_list()
8585
});
8686

8787
list_apply(&mut group, "same_len_concrete_patch", full_base, patch);
8888

8989
list_apply(&mut group, "same_len_dynamic_patch", full_base, |size| {
90-
patch(size).clone_dynamic()
90+
patch(size).to_dynamic_list()
9191
});
9292

9393
group.finish();
9494
}
9595

96-
fn concrete_list_clone_dynamic(criterion: &mut Criterion) {
97-
let mut group = create_group(criterion, bench!("concrete_list_clone_dynamic"));
96+
fn concrete_list_to_dynamic_list(criterion: &mut Criterion) {
97+
let mut group = create_group(criterion, bench!("concrete_list_to_dynamic_list"));
9898

9999
for size in SIZES {
100100
group.throughput(Throughput::Elements(size as u64));
@@ -105,7 +105,7 @@ fn concrete_list_clone_dynamic(criterion: &mut Criterion) {
105105
|bencher, &size| {
106106
let v = iter::repeat_n(0, size).collect::<Vec<_>>();
107107

108-
bencher.iter(|| black_box(&v).clone_dynamic());
108+
bencher.iter(|| black_box(&v).to_dynamic_list());
109109
},
110110
);
111111
}
@@ -127,7 +127,7 @@ fn dynamic_list_push(criterion: &mut Criterion) {
127127
let dst = DynamicList::default();
128128

129129
bencher.iter_batched(
130-
|| (src.clone(), dst.clone_dynamic()),
130+
|| (src.clone(), dst.to_dynamic_list()),
131131
|(src, mut dst)| {
132132
for item in src {
133133
dst.push(item);
@@ -145,20 +145,20 @@ fn dynamic_list_push(criterion: &mut Criterion) {
145145
fn dynamic_list_apply(criterion: &mut Criterion) {
146146
let mut group = create_group(criterion, bench!("dynamic_list_apply"));
147147

148-
let empty_base = |_: usize| || Vec::<u64>::new().clone_dynamic();
148+
let empty_base = |_: usize| || Vec::<u64>::new().to_dynamic_list();
149149
let full_base = |size: usize| move || iter::repeat_n(0, size).collect::<Vec<u64>>();
150150
let patch = |size: usize| iter::repeat_n(1, size).collect::<Vec<u64>>();
151151

152152
list_apply(&mut group, "empty_base_concrete_patch", empty_base, patch);
153153

154154
list_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
155-
patch(size).clone_dynamic()
155+
patch(size).to_dynamic_list()
156156
});
157157

158158
list_apply(&mut group, "same_len_concrete_patch", full_base, patch);
159159

160160
list_apply(&mut group, "same_len_dynamic_patch", full_base, |size| {
161-
patch(size).clone_dynamic()
161+
patch(size).to_dynamic_list()
162162
});
163163

164164
group.finish();

benches/benches/bevy_reflect/map.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
108108
);
109109

110110
map_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
111-
key_range_patch(size).clone_dynamic()
111+
key_range_patch(size).to_dynamic_map()
112112
});
113113

114114
map_apply(
@@ -122,7 +122,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
122122
&mut group,
123123
"same_keys_dynamic_patch",
124124
key_range_base,
125-
|size| key_range_patch(size).clone_dynamic(),
125+
|size| key_range_patch(size).to_dynamic_map(),
126126
);
127127

128128
map_apply(
@@ -136,7 +136,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
136136
&mut group,
137137
"disjoint_keys_dynamic_patch",
138138
key_range_base,
139-
|size| disjoint_patch(size).clone_dynamic(),
139+
|size| disjoint_patch(size).to_dynamic_map(),
140140
);
141141
}
142142

@@ -159,7 +159,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
159159
(0..size as u64)
160160
.zip(iter::repeat(0))
161161
.collect::<HashMap<u64, u64>>()
162-
.clone_dynamic()
162+
.to_dynamic_map()
163163
}
164164
};
165165

@@ -183,7 +183,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
183183
);
184184

185185
map_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
186-
key_range_patch(size).clone_dynamic()
186+
key_range_patch(size).to_dynamic_map()
187187
});
188188

189189
map_apply(
@@ -197,7 +197,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
197197
&mut group,
198198
"same_keys_dynamic_patch",
199199
key_range_base,
200-
|size| key_range_patch(size).clone_dynamic(),
200+
|size| key_range_patch(size).to_dynamic_map(),
201201
);
202202

203203
map_apply(
@@ -211,7 +211,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
211211
&mut group,
212212
"disjoint_keys_dynamic_patch",
213213
key_range_base,
214-
|size| disjoint_patch(size).clone_dynamic(),
214+
|size| disjoint_patch(size).to_dynamic_map(),
215215
);
216216
}
217217

benches/benches/bevy_reflect/struct.rs

+18-18
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ criterion_group!(
1212
concrete_struct_apply,
1313
concrete_struct_field,
1414
concrete_struct_type_info,
15-
concrete_struct_clone,
16-
dynamic_struct_clone,
15+
concrete_struct_to_dynamic_struct,
16+
dynamic_struct_to_dynamic_struct,
1717
dynamic_struct_apply,
1818
dynamic_struct_get_field,
1919
dynamic_struct_insert,
@@ -113,7 +113,7 @@ fn concrete_struct_apply(criterion: &mut Criterion) {
113113
bencher.iter_batched(
114114
|| {
115115
let (obj, _) = input();
116-
let patch = obj.clone_dynamic();
116+
let patch = obj.to_dynamic_struct();
117117
(obj, patch)
118118
},
119119
|(mut obj, patch)| obj.apply(black_box(&patch)),
@@ -170,8 +170,8 @@ fn concrete_struct_type_info(criterion: &mut Criterion) {
170170
}
171171
}
172172

173-
fn concrete_struct_clone(criterion: &mut Criterion) {
174-
let mut group = create_group(criterion, bench!("concrete_struct_clone"));
173+
fn concrete_struct_to_dynamic_struct(criterion: &mut Criterion) {
174+
let mut group = create_group(criterion, bench!("concrete_struct_to_dynamic_struct"));
175175

176176
let structs: [(Box<dyn Struct>, Box<dyn Struct>); 5] = [
177177
(
@@ -203,28 +203,28 @@ fn concrete_struct_clone(criterion: &mut Criterion) {
203203
BenchmarkId::new("NonGeneric", field_count),
204204
&standard,
205205
|bencher, s| {
206-
bencher.iter(|| s.clone_dynamic());
206+
bencher.iter(|| s.to_dynamic_struct());
207207
},
208208
);
209209
group.bench_with_input(
210210
BenchmarkId::new("Generic", field_count),
211211
&generic,
212212
|bencher, s| {
213-
bencher.iter(|| s.clone_dynamic());
213+
bencher.iter(|| s.to_dynamic_struct());
214214
},
215215
);
216216
}
217217
}
218218

219-
fn dynamic_struct_clone(criterion: &mut Criterion) {
220-
let mut group = create_group(criterion, bench!("dynamic_struct_clone"));
219+
fn dynamic_struct_to_dynamic_struct(criterion: &mut Criterion) {
220+
let mut group = create_group(criterion, bench!("dynamic_struct_to_dynamic_struct"));
221221

222222
let structs: [Box<dyn Struct>; 5] = [
223-
Box::new(Struct1::default().clone_dynamic()),
224-
Box::new(Struct16::default().clone_dynamic()),
225-
Box::new(Struct32::default().clone_dynamic()),
226-
Box::new(Struct64::default().clone_dynamic()),
227-
Box::new(Struct128::default().clone_dynamic()),
223+
Box::new(Struct1::default().to_dynamic_struct()),
224+
Box::new(Struct16::default().to_dynamic_struct()),
225+
Box::new(Struct32::default().to_dynamic_struct()),
226+
Box::new(Struct64::default().to_dynamic_struct()),
227+
Box::new(Struct128::default().to_dynamic_struct()),
228228
];
229229

230230
for s in structs {
@@ -234,7 +234,7 @@ fn dynamic_struct_clone(criterion: &mut Criterion) {
234234
BenchmarkId::from_parameter(field_count),
235235
&s,
236236
|bencher, s| {
237-
bencher.iter(|| s.clone_dynamic());
237+
bencher.iter(|| s.to_dynamic_struct());
238238
},
239239
);
240240
}
@@ -265,7 +265,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
265265
&patch,
266266
|bencher, patch| {
267267
bencher.iter_batched(
268-
|| (base.clone_dynamic(), patch()),
268+
|| (base.to_dynamic_struct(), patch()),
269269
|(mut base, patch)| base.apply(black_box(&*patch)),
270270
BatchSize::SmallInput,
271271
);
@@ -289,7 +289,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
289289
}
290290

291291
bencher.iter_batched(
292-
|| base.clone_dynamic(),
292+
|| base.to_dynamic_struct(),
293293
|mut base| base.apply(black_box(&patch)),
294294
BatchSize::SmallInput,
295295
);
@@ -315,7 +315,7 @@ fn dynamic_struct_insert(criterion: &mut Criterion) {
315315

316316
let field = format!("field_{}", field_count);
317317
bencher.iter_batched(
318-
|| s.clone_dynamic(),
318+
|| s.to_dynamic_struct(),
319319
|mut s| {
320320
s.insert(black_box(&field), ());
321321
},

crates/bevy_asset/src/handle.rs

+12-4
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ mod tests {
658658
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
659659
}
660660

661-
/// `Reflect::clone_value` should increase the strong count of a strong handle
661+
/// `PartialReflect::reflect_clone`/`PartialReflect::to_dynamic` should increase the strong count of a strong handle
662662
#[test]
663663
fn strong_handle_reflect_clone() {
664664
use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
@@ -689,18 +689,26 @@ mod tests {
689689
);
690690

691691
let reflected: &dyn Reflect = &handle;
692-
let cloned_handle: Box<dyn PartialReflect> = reflected.clone_value();
692+
let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
693693

694694
assert_eq!(
695695
Arc::strong_count(strong),
696696
2,
697697
"Cloning the handle with reflect should increase the strong count to 2"
698698
);
699699

700+
let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
701+
702+
assert_eq!(
703+
Arc::strong_count(strong),
704+
3,
705+
"Converting the handle to a dynamic should increase the strong count to 3"
706+
);
707+
700708
let from_reflect_handle: Handle<MyAsset> =
701-
FromReflect::from_reflect(&*cloned_handle).unwrap();
709+
FromReflect::from_reflect(&*dynamic_handle).unwrap();
702710

703-
assert_eq!(Arc::strong_count(strong), 3, "Converting the reflected value back to a handle should increase the strong count to 3");
711+
assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
704712
assert!(
705713
from_reflect_handle.is_strong(),
706714
"The cloned handle should still be strong"

crates/bevy_ecs/src/component.rs

+24-3
Original file line numberDiff line numberDiff line change
@@ -2911,12 +2911,14 @@ pub fn component_clone_via_clone<C: Clone + Component>(
29112911
/// - Component has [`TypeId`]
29122912
/// - Component is registered
29132913
/// - Component has [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr) registered
2914-
/// - Component has one of the following registered: [`ReflectFromReflect`](bevy_reflect::ReflectFromReflect),
2914+
/// - Component can be cloned via [`PartialReflect::reflect_clone`] _or_ has one of the following registered: [`ReflectFromReflect`](bevy_reflect::ReflectFromReflect),
29152915
/// [`ReflectDefault`](bevy_reflect::std_traits::ReflectDefault), [`ReflectFromWorld`](crate::reflect::ReflectFromWorld)
29162916
///
29172917
/// If any of the conditions is not satisfied, the component will be skipped.
29182918
///
29192919
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
2920+
///
2921+
/// [`PartialReflect::reflect_clone`]: bevy_reflect::PartialReflect::reflect_clone
29202922
#[cfg(feature = "bevy_reflect")]
29212923
pub fn component_clone_via_reflect(
29222924
commands: &mut Commands,
@@ -2934,6 +2936,21 @@ pub fn component_clone_via_reflect(
29342936
// checked in read_source_component_reflect
29352937
let type_id = component_info.type_id().unwrap();
29362938

2939+
// Try to clone using `reflect_clone`
2940+
if let Ok(mut component) = source_component_reflect.reflect_clone() {
2941+
if let Some(reflect_component) =
2942+
registry.get_type_data::<crate::reflect::ReflectComponent>(type_id)
2943+
{
2944+
reflect_component.visit_entities_mut(&mut *component, &mut |entity| {
2945+
*entity = ctx.entity_mapper().get_mapped(*entity);
2946+
});
2947+
}
2948+
drop(registry);
2949+
2950+
ctx.write_target_component_reflect(component);
2951+
return;
2952+
}
2953+
29372954
// Try to clone using ReflectFromReflect
29382955
if let Some(reflect_from_reflect) =
29392956
registry.get_type_data::<bevy_reflect::ReflectFromReflect>(type_id)
@@ -2977,7 +2994,7 @@ pub fn component_clone_via_reflect(
29772994
mapped_entities.push(entity);
29782995
});
29792996
}
2980-
let source_component_cloned = source_component_reflect.clone_value();
2997+
let source_component_cloned = source_component_reflect.to_dynamic();
29812998
let component_layout = component_info.layout();
29822999
let target = ctx.target();
29833000
let component_id = ctx.component_id();
@@ -3008,7 +3025,11 @@ pub fn component_clone_via_reflect(
30083025
world
30093026
.entity_mut(target)
30103027
.insert_by_id(component_id, OwningPtr::new(raw_component_ptr));
3011-
alloc::alloc::dealloc(raw_component_ptr.as_ptr(), component_layout);
3028+
3029+
if component_layout.size() > 0 {
3030+
// Ensure we don't attempt to deallocate zero-sized components
3031+
alloc::alloc::dealloc(raw_component_ptr.as_ptr(), component_layout);
3032+
}
30123033
}
30133034
});
30143035
}

0 commit comments

Comments
 (0)