1
1
use core:: hint:: black_box;
2
2
3
3
use benches:: bench;
4
- use bevy_ecs:: bundle:: { Bundle , StaticBundle } ;
4
+ use bevy_ecs:: bundle:: { Bundle , InsertMode , StaticBundle } ;
5
5
use bevy_ecs:: component:: ComponentCloneBehavior ;
6
6
use bevy_ecs:: entity:: EntityCloner ;
7
7
use bevy_ecs:: hierarchy:: ChildOf ;
@@ -17,41 +17,15 @@ criterion_group!(
17
17
hierarchy_tall,
18
18
hierarchy_wide,
19
19
hierarchy_many,
20
+ filter
20
21
) ;
21
22
22
23
#[ derive( Component , Reflect , Default , Clone ) ]
23
- struct C1 ( Mat4 ) ;
24
+ struct C < const N : usize > ( Mat4 ) ;
24
25
25
- #[ derive( Component , Reflect , Default , Clone ) ]
26
- struct C2 ( Mat4 ) ;
27
-
28
- #[ derive( Component , Reflect , Default , Clone ) ]
29
- struct C3 ( Mat4 ) ;
30
-
31
- #[ derive( Component , Reflect , Default , Clone ) ]
32
- struct C4 ( Mat4 ) ;
33
-
34
- #[ derive( Component , Reflect , Default , Clone ) ]
35
- struct C5 ( Mat4 ) ;
36
-
37
- #[ derive( Component , Reflect , Default , Clone ) ]
38
- struct C6 ( Mat4 ) ;
39
-
40
- #[ derive( Component , Reflect , Default , Clone ) ]
41
- struct C7 ( Mat4 ) ;
42
-
43
- #[ derive( Component , Reflect , Default , Clone ) ]
44
- struct C8 ( Mat4 ) ;
45
-
46
- #[ derive( Component , Reflect , Default , Clone ) ]
47
- struct C9 ( Mat4 ) ;
26
+ type ComplexBundle = ( C < 1 > , C < 2 > , C < 3 > , C < 4 > , C < 5 > , C < 6 > , C < 7 > , C < 8 > , C < 9 > , C < 10 > ) ;
48
27
49
- #[ derive( Component , Reflect , Default , Clone ) ]
50
- struct C10 ( Mat4 ) ;
51
-
52
- type ComplexBundle = ( C1 , C2 , C3 , C4 , C5 , C6 , C7 , C8 , C9 , C10 ) ;
53
-
54
- /// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to
28
+ /// Sets the [`ComponentCloneBehavior`] for all explicit and required components in a bundle `B` to
55
29
/// use the [`Reflect`] trait instead of [`Clone`].
56
30
fn reflection_cloner < B : StaticBundle + GetTypeRegistration > (
57
31
world : & mut World ,
@@ -71,7 +45,7 @@ fn reflection_cloner<B: StaticBundle + GetTypeRegistration>(
71
45
// this bundle are saved.
72
46
let component_ids: Vec < _ > = world. register_bundle :: < B > ( ) . contributed_components ( ) . into ( ) ;
73
47
74
- let mut builder = EntityCloner :: build ( world) ;
48
+ let mut builder = EntityCloner :: build_opt_out ( world) ;
75
49
76
50
// Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`.
77
51
for component in component_ids {
@@ -82,16 +56,15 @@ fn reflection_cloner<B: StaticBundle + GetTypeRegistration>(
82
56
builder. finish ( )
83
57
}
84
58
85
- /// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
86
- /// bundle `B`.
59
+ /// A helper function that benchmarks running [`EntityCloner::spawn_clone`] with a bundle `B`.
87
60
///
88
61
/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned
89
62
/// in the benchmark.
90
63
///
91
- /// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler `] for all
92
- /// components (which is usually [`ComponentCloneHandler::clone_handler ()`]). If `clone_via_reflect`
64
+ /// If `clone_via_reflect` is false, this will use the default [`ComponentCloneBehavior `] for all
65
+ /// components (which is usually [`ComponentCloneBehavior::clone ()`]). If `clone_via_reflect`
93
66
/// is true, it will overwrite the handler for all components in the bundle to be
94
- /// [`ComponentCloneHandler::reflect_handler ()`].
67
+ /// [`ComponentCloneBehavior::reflect ()`].
95
68
fn bench_clone < B : Bundle + StaticBundle + Default + GetTypeRegistration > (
96
69
b : & mut Bencher ,
97
70
clone_via_reflect : bool ,
@@ -114,8 +87,7 @@ fn bench_clone<B: Bundle + StaticBundle + Default + GetTypeRegistration>(
114
87
} ) ;
115
88
}
116
89
117
- /// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
118
- /// bundle `B`.
90
+ /// A helper function that benchmarks running [`EntityCloner::spawn_clone`] with a bundle `B`.
119
91
///
120
92
/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several
121
93
/// children. It does so by setting up an entity tree with a given `height` where each entity has a
@@ -135,7 +107,7 @@ fn bench_clone_hierarchy<B: Bundle + StaticBundle + Default + GetTypeRegistratio
135
107
let mut cloner = if clone_via_reflect {
136
108
reflection_cloner :: < B > ( & mut world, true )
137
109
} else {
138
- let mut builder = EntityCloner :: build ( & mut world) ;
110
+ let mut builder = EntityCloner :: build_opt_out ( & mut world) ;
139
111
builder. linked_cloning ( true ) ;
140
112
builder. finish ( )
141
113
} ;
@@ -169,7 +141,7 @@ fn bench_clone_hierarchy<B: Bundle + StaticBundle + Default + GetTypeRegistratio
169
141
170
142
// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This
171
143
// constant represents this as an easy array that can be used in a `for` loop.
172
- const SCENARIOS : [ ( & str , bool ) ; 2 ] = [ ( "clone" , false ) , ( "reflect" , true ) ] ;
144
+ const CLONE_SCENARIOS : [ ( & str , bool ) ; 2 ] = [ ( "clone" , false ) , ( "reflect" , true ) ] ;
173
145
174
146
/// Benchmarks cloning a single entity with 10 components and no children.
175
147
fn single ( c : & mut Criterion ) {
@@ -178,7 +150,7 @@ fn single(c: &mut Criterion) {
178
150
// We're cloning 1 entity.
179
151
group. throughput ( Throughput :: Elements ( 1 ) ) ;
180
152
181
- for ( id, clone_via_reflect) in SCENARIOS {
153
+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
182
154
group. bench_function ( id, |b| {
183
155
bench_clone :: < ComplexBundle > ( b, clone_via_reflect) ;
184
156
} ) ;
@@ -194,9 +166,9 @@ fn hierarchy_tall(c: &mut Criterion) {
194
166
// We're cloning both the root entity and its 50 descendents.
195
167
group. throughput ( Throughput :: Elements ( 51 ) ) ;
196
168
197
- for ( id, clone_via_reflect) in SCENARIOS {
169
+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
198
170
group. bench_function ( id, |b| {
199
- bench_clone_hierarchy :: < C1 > ( b, 50 , 1 , clone_via_reflect) ;
171
+ bench_clone_hierarchy :: < C < 1 > > ( b, 50 , 1 , clone_via_reflect) ;
200
172
} ) ;
201
173
}
202
174
@@ -210,9 +182,9 @@ fn hierarchy_wide(c: &mut Criterion) {
210
182
// We're cloning both the root entity and its 50 direct children.
211
183
group. throughput ( Throughput :: Elements ( 51 ) ) ;
212
184
213
- for ( id, clone_via_reflect) in SCENARIOS {
185
+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
214
186
group. bench_function ( id, |b| {
215
- bench_clone_hierarchy :: < C1 > ( b, 1 , 50 , clone_via_reflect) ;
187
+ bench_clone_hierarchy :: < C < 1 > > ( b, 1 , 50 , clone_via_reflect) ;
216
188
} ) ;
217
189
}
218
190
@@ -228,11 +200,165 @@ fn hierarchy_many(c: &mut Criterion) {
228
200
// of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :)
229
201
group. throughput ( Throughput :: Elements ( 364 ) ) ;
230
202
231
- for ( id, clone_via_reflect) in SCENARIOS {
203
+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
232
204
group. bench_function ( id, |b| {
233
205
bench_clone_hierarchy :: < ComplexBundle > ( b, 5 , 3 , clone_via_reflect) ;
234
206
} ) ;
235
207
}
236
208
237
209
group. finish ( ) ;
238
210
}
211
+
212
+ /// Filter scenario variant for bot opt-in and opt-out filters
213
+ #[ derive( Clone , Copy ) ]
214
+ #[ expect(
215
+ clippy:: enum_variant_names,
216
+ reason = "'Opt' is not understood as an prefix but `OptOut'/'OptIn' are"
217
+ ) ]
218
+ enum FilterScenario {
219
+ OptOutNone ,
220
+ OptOutNoneKeep ( bool ) ,
221
+ OptOutAll ,
222
+ OptInNone ,
223
+ OptInAll ,
224
+ OptInAllWithoutRequired ,
225
+ OptInAllKeep ( bool ) ,
226
+ OptInAllKeepWithoutRequired ( bool ) ,
227
+ }
228
+
229
+ impl From < FilterScenario > for String {
230
+ fn from ( value : FilterScenario ) -> Self {
231
+ match value {
232
+ FilterScenario :: OptOutNone => "opt_out_none" ,
233
+ FilterScenario :: OptOutNoneKeep ( true ) => "opt_out_none_keep_none" ,
234
+ FilterScenario :: OptOutNoneKeep ( false ) => "opt_out_none_keep_all" ,
235
+ FilterScenario :: OptOutAll => "opt_out_all" ,
236
+ FilterScenario :: OptInNone => "opt_in_none" ,
237
+ FilterScenario :: OptInAll => "opt_in_all" ,
238
+ FilterScenario :: OptInAllWithoutRequired => "opt_in_all_without_required" ,
239
+ FilterScenario :: OptInAllKeep ( true ) => "opt_in_all_keep_none" ,
240
+ FilterScenario :: OptInAllKeep ( false ) => "opt_in_all_keep_all" ,
241
+ FilterScenario :: OptInAllKeepWithoutRequired ( true ) => {
242
+ "opt_in_all_keep_none_without_required"
243
+ }
244
+ FilterScenario :: OptInAllKeepWithoutRequired ( false ) => {
245
+ "opt_in_all_keep_all_without_required"
246
+ }
247
+ }
248
+ . into ( )
249
+ }
250
+ }
251
+
252
+ /// Common scenarios for different filter to be benchmarked.
253
+ const FILTER_SCENARIOS : [ FilterScenario ; 11 ] = [
254
+ FilterScenario :: OptOutNone ,
255
+ FilterScenario :: OptOutNoneKeep ( true ) ,
256
+ FilterScenario :: OptOutNoneKeep ( false ) ,
257
+ FilterScenario :: OptOutAll ,
258
+ FilterScenario :: OptInNone ,
259
+ FilterScenario :: OptInAll ,
260
+ FilterScenario :: OptInAllWithoutRequired ,
261
+ FilterScenario :: OptInAllKeep ( true ) ,
262
+ FilterScenario :: OptInAllKeep ( false ) ,
263
+ FilterScenario :: OptInAllKeepWithoutRequired ( true ) ,
264
+ FilterScenario :: OptInAllKeepWithoutRequired ( false ) ,
265
+ ] ;
266
+
267
+ /// A helper function that benchmarks running [`EntityCloner::clone_entity`] with a bundle `B`.
268
+ ///
269
+ /// The bundle must implement [`Default`], which is used to create the first entity that gets its components cloned
270
+ /// in the benchmark. It may also be used to populate the target entity depending on the scenario.
271
+ fn bench_filter < B : StaticBundle + Default > ( b : & mut Bencher , scenario : FilterScenario ) {
272
+ let mut world = World :: default ( ) ;
273
+ let mut spawn = |empty| match empty {
274
+ false => world. spawn ( B :: default ( ) ) . id ( ) ,
275
+ true => world. spawn_empty ( ) . id ( ) ,
276
+ } ;
277
+ let source = spawn ( false ) ;
278
+ let ( target, mut cloner) ;
279
+
280
+ match scenario {
281
+ FilterScenario :: OptOutNone => {
282
+ target = spawn ( true ) ;
283
+ cloner = EntityCloner :: default ( ) ;
284
+ }
285
+ FilterScenario :: OptOutNoneKeep ( is_new) => {
286
+ target = spawn ( is_new) ;
287
+ let mut builder = EntityCloner :: build_opt_out ( & mut world) ;
288
+ builder. insert_mode ( InsertMode :: Keep ) ;
289
+ cloner = builder. finish ( ) ;
290
+ }
291
+ FilterScenario :: OptOutAll => {
292
+ target = spawn ( true ) ;
293
+ let mut builder = EntityCloner :: build_opt_out ( & mut world) ;
294
+ builder. deny :: < B > ( ) ;
295
+ cloner = builder. finish ( ) ;
296
+ }
297
+ FilterScenario :: OptInNone => {
298
+ target = spawn ( true ) ;
299
+ let builder = EntityCloner :: build_opt_in ( & mut world) ;
300
+ cloner = builder. finish ( ) ;
301
+ }
302
+ FilterScenario :: OptInAll => {
303
+ target = spawn ( true ) ;
304
+ let mut builder = EntityCloner :: build_opt_in ( & mut world) ;
305
+ builder. allow :: < B > ( ) ;
306
+ cloner = builder. finish ( ) ;
307
+ }
308
+ FilterScenario :: OptInAllWithoutRequired => {
309
+ target = spawn ( true ) ;
310
+ let mut builder = EntityCloner :: build_opt_in ( & mut world) ;
311
+ builder. without_required_components ( |builder| {
312
+ builder. allow :: < B > ( ) ;
313
+ } ) ;
314
+ cloner = builder. finish ( ) ;
315
+ }
316
+ FilterScenario :: OptInAllKeep ( is_new) => {
317
+ target = spawn ( is_new) ;
318
+ let mut builder = EntityCloner :: build_opt_in ( & mut world) ;
319
+ builder. allow_if_new :: < B > ( ) ;
320
+ cloner = builder. finish ( ) ;
321
+ }
322
+ FilterScenario :: OptInAllKeepWithoutRequired ( is_new) => {
323
+ target = spawn ( is_new) ;
324
+ let mut builder = EntityCloner :: build_opt_in ( & mut world) ;
325
+ builder. without_required_components ( |builder| {
326
+ builder. allow_if_new :: < B > ( ) ;
327
+ } ) ;
328
+ cloner = builder. finish ( ) ;
329
+ }
330
+ }
331
+
332
+ b. iter ( || {
333
+ // clones the given entity into the target
334
+ cloner. clone_entity ( & mut world, black_box ( source) , black_box ( target) ) ;
335
+ world. flush ( ) ;
336
+ } ) ;
337
+ }
338
+
339
+ /// Benchmarks filtering of cloning a single entity with 5 unclonable components (each requiring 1 unclonable component) into a target.
340
+ fn filter ( c : & mut Criterion ) {
341
+ #[ derive( Component , Default ) ]
342
+ #[ component( clone_behavior = Ignore ) ]
343
+ struct C < const N : usize > ;
344
+
345
+ #[ derive( Component , Default ) ]
346
+ #[ component( clone_behavior = Ignore ) ]
347
+ #[ require( C :: <N >) ]
348
+ struct R < const N : usize > ;
349
+
350
+ type RequiringBundle = ( R < 1 > , R < 2 > , R < 3 > , R < 4 > , R < 5 > ) ;
351
+
352
+ let mut group = c. benchmark_group ( bench ! ( "filter" ) ) ;
353
+
354
+ // We're cloning 1 entity into a target.
355
+ group. throughput ( Throughput :: Elements ( 1 ) ) ;
356
+
357
+ for scenario in FILTER_SCENARIOS {
358
+ group. bench_function ( scenario, |b| {
359
+ bench_filter :: < RequiringBundle > ( b, scenario) ;
360
+ } ) ;
361
+ }
362
+
363
+ group. finish ( ) ;
364
+ }
0 commit comments