Skip to content

Commit 0521efe

Browse files
committed
Merge remote-tracking branch 'upstream/main' into split-bundle
2 parents 9711258 + bd4258b commit 0521efe

File tree

99 files changed

+3339
-791
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+3339
-791
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ env:
1515
# If nightly is breaking CI, modify this variable to target a specific nightly version.
1616
NIGHTLY_TOOLCHAIN: nightly
1717
RUSTFLAGS: "-D warnings"
18-
BINSTALL_VERSION: "v1.12.3"
18+
BINSTALL_VERSION: "v1.12.5"
1919

2020
concurrency:
2121
group: ${{github.workflow}}-${{github.ref}}
@@ -272,7 +272,7 @@ jobs:
272272
timeout-minutes: 30
273273
steps:
274274
- uses: actions/checkout@v4
275-
- uses: cargo-bins/[email protected].3
275+
- uses: cargo-bins/[email protected].5
276276
- name: Install taplo
277277
run: cargo binstall [email protected] --locked
278278
- name: Run Taplo

Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ trace = ["bevy_internal/trace", "dep:tracing"]
330330
# Basis Universal compressed texture support
331331
basis-universal = ["bevy_internal/basis-universal"]
332332

333+
# Enables compressed KTX2 UASTC texture output on the asset processor
334+
compressed_image_saver = ["bevy_internal/compressed_image_saver"]
335+
333336
# BMP image format support
334337
bmp = ["bevy_internal/bmp"]
335338

@@ -845,6 +848,17 @@ description = "Generates a texture atlas (sprite sheet) from individual sprites"
845848
category = "2D Rendering"
846849
wasm = false
847850

851+
[[example]]
852+
name = "tilemap_chunk"
853+
path = "examples/2d/tilemap_chunk.rs"
854+
doc-scrape-examples = true
855+
856+
[package.metadata.example.tilemap_chunk]
857+
name = "Tilemap Chunk"
858+
description = "Renders a tilemap chunk"
859+
category = "2D Rendering"
860+
wasm = true
861+
848862
[[example]]
849863
name = "transparency_2d"
850864
path = "examples/2d/transparency_2d.rs"

benches/benches/bevy_ecs/entity_cloning.rs

Lines changed: 173 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use core::hint::black_box;
22

33
use benches::bench;
4-
use bevy_ecs::bundle::{Bundle, StaticBundle};
4+
use bevy_ecs::bundle::{Bundle, InsertMode, StaticBundle};
55
use bevy_ecs::component::ComponentCloneBehavior;
66
use bevy_ecs::entity::EntityCloner;
77
use 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`].
5630
fn 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()`].
9568
fn 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.
175147
fn 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: 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+
}

crates/bevy_animation/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ serde = "1"
3737
blake3 = { version = "1.0" }
3838
downcast-rs = { version = "2", default-features = false, features = ["std"] }
3939
thiserror = { version = "2", default-features = false }
40-
derive_more = { version = "1", default-features = false, features = ["from"] }
40+
derive_more = { version = "2", default-features = false, features = ["from"] }
4141
either = "1.13"
4242
thread_local = "1"
4343
uuid = { version = "1.13.1", features = ["v4"] }

0 commit comments

Comments
 (0)