11use core:: hint:: black_box;
22
33use benches:: bench;
4- use bevy_ecs:: bundle:: { Bundle , StaticBundle } ;
4+ use bevy_ecs:: bundle:: { Bundle , InsertMode , StaticBundle } ;
55use bevy_ecs:: component:: ComponentCloneBehavior ;
66use bevy_ecs:: entity:: EntityCloner ;
77use bevy_ecs:: hierarchy:: ChildOf ;
@@ -17,41 +17,15 @@ criterion_group!(
1717 hierarchy_tall,
1818 hierarchy_wide,
1919 hierarchy_many,
20+ filter
2021) ;
2122
2223#[ derive( Component , Reflect , Default , Clone ) ]
23- struct C1 ( Mat4 ) ;
24+ struct C < const N : usize > ( Mat4 ) ;
2425
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 > ) ;
4827
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
5529/// use the [`Reflect`] trait instead of [`Clone`].
5630fn reflection_cloner < B : StaticBundle + GetTypeRegistration > (
5731 world : & mut World ,
@@ -71,7 +45,7 @@ fn reflection_cloner<B: StaticBundle + GetTypeRegistration>(
7145 // this bundle are saved.
7246 let component_ids: Vec < _ > = world. register_bundle :: < B > ( ) . contributed_components ( ) . into ( ) ;
7347
74- let mut builder = EntityCloner :: build ( world) ;
48+ let mut builder = EntityCloner :: build_opt_out ( world) ;
7549
7650 // Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`.
7751 for component in component_ids {
@@ -82,16 +56,15 @@ fn reflection_cloner<B: StaticBundle + GetTypeRegistration>(
8256 builder. finish ( )
8357}
8458
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`.
8760///
8861/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned
8962/// in the benchmark.
9063///
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`
9366/// is true, it will overwrite the handler for all components in the bundle to be
94- /// [`ComponentCloneHandler::reflect_handler ()`].
67+ /// [`ComponentCloneBehavior::reflect ()`].
9568fn bench_clone < B : Bundle + StaticBundle + Default + GetTypeRegistration > (
9669 b : & mut Bencher ,
9770 clone_via_reflect : bool ,
@@ -114,8 +87,7 @@ fn bench_clone<B: Bundle + StaticBundle + Default + GetTypeRegistration>(
11487 } ) ;
11588}
11689
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`.
11991///
12092/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several
12193/// 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
135107 let mut cloner = if clone_via_reflect {
136108 reflection_cloner :: < B > ( & mut world, true )
137109 } else {
138- let mut builder = EntityCloner :: build ( & mut world) ;
110+ let mut builder = EntityCloner :: build_opt_out ( & mut world) ;
139111 builder. linked_cloning ( true ) ;
140112 builder. finish ( )
141113 } ;
@@ -169,7 +141,7 @@ fn bench_clone_hierarchy<B: Bundle + StaticBundle + Default + GetTypeRegistratio
169141
170142// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This
171143// 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 ) ] ;
173145
174146/// Benchmarks cloning a single entity with 10 components and no children.
175147fn single ( c : & mut Criterion ) {
@@ -178,7 +150,7 @@ fn single(c: &mut Criterion) {
178150 // We're cloning 1 entity.
179151 group. throughput ( Throughput :: Elements ( 1 ) ) ;
180152
181- for ( id, clone_via_reflect) in SCENARIOS {
153+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
182154 group. bench_function ( id, |b| {
183155 bench_clone :: < ComplexBundle > ( b, clone_via_reflect) ;
184156 } ) ;
@@ -194,9 +166,9 @@ fn hierarchy_tall(c: &mut Criterion) {
194166 // We're cloning both the root entity and its 50 descendents.
195167 group. throughput ( Throughput :: Elements ( 51 ) ) ;
196168
197- for ( id, clone_via_reflect) in SCENARIOS {
169+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
198170 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) ;
200172 } ) ;
201173 }
202174
@@ -210,9 +182,9 @@ fn hierarchy_wide(c: &mut Criterion) {
210182 // We're cloning both the root entity and its 50 direct children.
211183 group. throughput ( Throughput :: Elements ( 51 ) ) ;
212184
213- for ( id, clone_via_reflect) in SCENARIOS {
185+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
214186 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) ;
216188 } ) ;
217189 }
218190
@@ -228,11 +200,165 @@ fn hierarchy_many(c: &mut Criterion) {
228200 // of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :)
229201 group. throughput ( Throughput :: Elements ( 364 ) ) ;
230202
231- for ( id, clone_via_reflect) in SCENARIOS {
203+ for ( id, clone_via_reflect) in CLONE_SCENARIOS {
232204 group. bench_function ( id, |b| {
233205 bench_clone_hierarchy :: < ComplexBundle > ( b, 5 , 3 , clone_via_reflect) ;
234206 } ) ;
235207 }
236208
237209 group. finish ( ) ;
238210}
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 : Bundle + 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