mirror of
https://github.com/bevyengine/bevy
synced 2024-11-23 05:03:47 +00:00
Merge branch 'main' into transmission
This commit is contained in:
commit
93377bc7c8
354 changed files with 7941 additions and 4926 deletions
|
@ -292,11 +292,10 @@ There are three main places you can check for things to review:
|
|||
2. Pull requests on [bevy](https://github.com/bevyengine/bevy/pulls) and the [bevy-website](https://github.com/bevyengine/bevy-website/pulls) repos.
|
||||
3. [RFCs](https://github.com/bevyengine/rfcs), which need extensive thoughtful community input on their design.
|
||||
|
||||
Official focus areas and work done by @cart go through this review process as well.
|
||||
Not even our project lead is exempt from reviews and RFCs!
|
||||
Not even our Project Leads and Maintainers are exempt from reviews and RFCs!
|
||||
By giving feedback on this work (and related supporting work), you can help us make sure our releases are both high-quality and timely.
|
||||
|
||||
Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message @cart for a Bevy org role to help us keep things tidy.
|
||||
Finally, if nothing brings you more satisfaction than seeing every last issue labeled and all resolved issues closed, feel free to message the Project Lead (currently @cart) for a Bevy org role to help us keep things tidy.
|
||||
As discussed in our [*Bevy Organization doc*](/docs/the_bevy_organization.md), this role only requires good faith and a basic understanding of our development process.
|
||||
|
||||
### How to adopt pull requests
|
||||
|
@ -340,8 +339,8 @@ To locally lint your files using the same workflow as our CI:
|
|||
1. Install [markdownlint-cli](https://github.com/igorshubovych/markdownlint-cli).
|
||||
2. Run `markdownlint -f -c .github/linters/.markdown-lint.yml .` in the root directory of the Bevy project.
|
||||
5. Push your changes to your fork on Github and open a Pull Request.
|
||||
6. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to @cart's final judgement.
|
||||
7. When your PR is ready to merge, @cart will review it and suggest final changes. If those changes are minimal he may even apply them directly to speed up merging.
|
||||
6. Respond to any CI failures or review feedback. While CI failures must be fixed before we can merge your PR, you do not need to *agree* with all feedback from your reviews, merely acknowledge that it was given. If you cannot come to an agreement, leave the thread open and defer to a Maintainer or Project Lead's final judgement.
|
||||
7. When your PR is ready to merge, a Maintainer or Project Lead will review it and suggest final changes. If those changes are minimal they may even apply them directly to speed up merging.
|
||||
|
||||
If you end up adding a new official Bevy crate to the `bevy` repo:
|
||||
|
||||
|
|
78
Cargo.toml
78
Cargo.toml
|
@ -49,6 +49,7 @@ default = [
|
|||
"vorbis",
|
||||
"x11",
|
||||
"filesystem_watcher",
|
||||
"bevy_gizmos",
|
||||
"android_shared_stdcxx",
|
||||
"tonemapping_luts",
|
||||
]
|
||||
|
@ -66,7 +67,7 @@ bevy_asset = ["bevy_internal/bevy_asset"]
|
|||
bevy_audio = ["bevy_internal/bevy_audio"]
|
||||
|
||||
# Provides cameras and other basic render pipeline features
|
||||
bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"]
|
||||
bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline", "bevy_asset", "bevy_render"]
|
||||
|
||||
# Plugin for dynamic loading (using [libloading](https://crates.io/crates/libloading))
|
||||
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
|
||||
|
@ -75,29 +76,32 @@ bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
|
|||
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
|
||||
|
||||
# [glTF](https://www.khronos.org/gltf/) support
|
||||
bevy_gltf = ["bevy_internal/bevy_gltf"]
|
||||
bevy_gltf = ["bevy_internal/bevy_gltf", "bevy_asset", "bevy_scene", "bevy_pbr"]
|
||||
|
||||
# Adds PBR rendering
|
||||
bevy_pbr = ["bevy_internal/bevy_pbr"]
|
||||
bevy_pbr = ["bevy_internal/bevy_pbr", "bevy_asset", "bevy_render", "bevy_core_pipeline"]
|
||||
|
||||
# Provides rendering functionality
|
||||
bevy_render = ["bevy_internal/bevy_render"]
|
||||
|
||||
# Provides scene functionality
|
||||
bevy_scene = ["bevy_internal/bevy_scene"]
|
||||
bevy_scene = ["bevy_internal/bevy_scene", "bevy_asset"]
|
||||
|
||||
# Provides sprite functionality
|
||||
bevy_sprite = ["bevy_internal/bevy_sprite"]
|
||||
bevy_sprite = ["bevy_internal/bevy_sprite", "bevy_render", "bevy_core_pipeline"]
|
||||
|
||||
# Provides text functionality
|
||||
bevy_text = ["bevy_internal/bevy_text"]
|
||||
|
||||
# A custom ECS-driven UI framework
|
||||
bevy_ui = ["bevy_internal/bevy_ui"]
|
||||
bevy_ui = ["bevy_internal/bevy_ui", "bevy_core_pipeline", "bevy_text", "bevy_sprite"]
|
||||
|
||||
# winit window and input backend
|
||||
bevy_winit = ["bevy_internal/bevy_winit"]
|
||||
|
||||
# Adds support for rendering gizmos
|
||||
bevy_gizmos = ["bevy_internal/bevy_gizmos"]
|
||||
|
||||
# Tracing support, saving a file in Chrome Tracing format
|
||||
trace_chrome = ["trace", "bevy_internal/trace_chrome"]
|
||||
|
||||
|
@ -309,6 +313,16 @@ description = "Renders a rectangle, circle, and hexagon"
|
|||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "2d_gizmos"
|
||||
path = "examples/2d/2d_gizmos.rs"
|
||||
|
||||
[package.metadata.example.2d_gizmos]
|
||||
name = "2D Gizmos"
|
||||
description = "A scene showcasing 2D gizmos"
|
||||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "sprite"
|
||||
path = "examples/2d/sprite.rs"
|
||||
|
@ -400,6 +414,16 @@ description = "A scene showcasing the built-in 3D shapes"
|
|||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "3d_gizmos"
|
||||
path = "examples/3d/3d_gizmos.rs"
|
||||
|
||||
[package.metadata.example.3d_gizmos]
|
||||
name = "3D Gizmos"
|
||||
description = "A scene showcasing 3D gizmos"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "atmospheric_fog"
|
||||
path = "examples/3d/atmospheric_fog.rs"
|
||||
|
@ -1395,11 +1419,21 @@ name = "post_processing"
|
|||
path = "examples/shader/post_processing.rs"
|
||||
|
||||
[package.metadata.example.post_processing]
|
||||
name = "Post Processing"
|
||||
name = "Post Processing - Render To Texture"
|
||||
description = "A custom post processing effect, using two cameras, with one reusing the render texture of the first one"
|
||||
category = "Shaders"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "post_process_pass"
|
||||
path = "examples/shader/post_process_pass.rs"
|
||||
|
||||
[package.metadata.example.post_process_pass]
|
||||
name = "Post Processing - Custom Render Pass"
|
||||
description = "A custom post processing effect, using a custom render pass that runs after the main pass"
|
||||
category = "Shaders"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "shader_defs"
|
||||
path = "examples/shader/shader_defs.rs"
|
||||
|
@ -1553,6 +1587,16 @@ description = "Simple benchmark to test per-entity draw overhead. Run with the `
|
|||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_gizmos"
|
||||
path = "examples/stress_tests/many_gizmos.rs"
|
||||
|
||||
[package.metadata.example.many_gizmos]
|
||||
name = "Many Gizmos"
|
||||
description = "Test rendering of many gizmos"
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_foxes"
|
||||
path = "examples/stress_tests/many_foxes.rs"
|
||||
|
@ -1563,6 +1607,16 @@ description = "Loads an animated fox model and spawns lots of them. Good for tes
|
|||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_glyphs"
|
||||
path = "examples/stress_tests/many_glyphs.rs"
|
||||
|
||||
[package.metadata.example.many_glyphs]
|
||||
name = "Many Glyphs"
|
||||
description = "Simple benchmark to test text rendering."
|
||||
category = "Stress Tests"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "many_lights"
|
||||
path = "examples/stress_tests/many_lights.rs"
|
||||
|
@ -1736,12 +1790,12 @@ category = "UI (User Interface)"
|
|||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "text_layout"
|
||||
path = "examples/ui/text_layout.rs"
|
||||
name = "flex_layout"
|
||||
path = "examples/ui/flex_layout.rs"
|
||||
|
||||
[package.metadata.example.text_layout]
|
||||
name = "Text Layout"
|
||||
description = "Demonstrates how the AlignItems and JustifyContent properties can be composed to layout text"
|
||||
[package.metadata.example.flex_layout]
|
||||
name = "Flex Layout"
|
||||
description = "Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text"
|
||||
category = "UI (User Interface)"
|
||||
wasm = false
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
(
|
||||
resources: {
|
||||
"scene::ResourceA": (
|
||||
score: 2,
|
||||
),
|
||||
},
|
||||
entities: {
|
||||
0: (
|
||||
components: {
|
||||
|
|
48
assets/shaders/post_process_pass.wgsl
Normal file
48
assets/shaders/post_process_pass.wgsl
Normal file
|
@ -0,0 +1,48 @@
|
|||
// This shader computes the chromatic aberration effect
|
||||
|
||||
#import bevy_pbr::utils
|
||||
|
||||
// Since post processing is a fullscreen effect, we use the fullscreen vertex shader provided by bevy.
|
||||
// This will import a vertex shader that renders a single fullscreen triangle.
|
||||
//
|
||||
// A fullscreen triangle is a single triangle that covers the entire screen.
|
||||
// The box in the top left in that diagram is the screen. The 4 x are the corner of the screen
|
||||
//
|
||||
// Y axis
|
||||
// 1 | x-----x......
|
||||
// 0 | | s | . ´
|
||||
// -1 | x_____x´
|
||||
// -2 | : .´
|
||||
// -3 | :´
|
||||
// +--------------- X axis
|
||||
// -1 0 1 2 3
|
||||
//
|
||||
// As you can see, the triangle ends up bigger than the screen.
|
||||
//
|
||||
// You don't need to worry about this too much since bevy will compute the correct UVs for you.
|
||||
#import bevy_core_pipeline::fullscreen_vertex_shader
|
||||
|
||||
@group(0) @binding(0)
|
||||
var screen_texture: texture_2d<f32>;
|
||||
@group(0) @binding(1)
|
||||
var texture_sampler: sampler;
|
||||
struct PostProcessSettings {
|
||||
intensity: f32,
|
||||
}
|
||||
@group(0) @binding(2)
|
||||
var<uniform> settings: PostProcessSettings;
|
||||
|
||||
@fragment
|
||||
fn fragment(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
|
||||
// Chromatic aberration strength
|
||||
let offset_strength = settings.intensity;
|
||||
|
||||
// Sample each color channel with an arbitrary shift
|
||||
return vec4<f32>(
|
||||
textureSample(screen_texture, texture_sampler, in.uv + vec2<f32>(offset_strength, -offset_strength)).r,
|
||||
textureSample(screen_texture, texture_sampler, in.uv + vec2<f32>(-offset_strength, 0.0)).g,
|
||||
textureSample(screen_texture, texture_sampler, in.uv + vec2<f32>(0.0, offset_strength)).b,
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ fn setup(system_count: usize) -> (World, Schedule) {
|
|||
fn empty() {}
|
||||
let mut schedule = Schedule::new();
|
||||
for _ in 0..system_count {
|
||||
schedule.add_system(empty);
|
||||
schedule.add_systems(empty);
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
(world, schedule)
|
||||
|
|
|
@ -154,7 +154,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
|||
let mut group = criterion.benchmark_group("empty_archetypes");
|
||||
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
|
||||
let (mut world, mut schedule) = setup(true, |schedule| {
|
||||
schedule.add_system(iter);
|
||||
schedule.add_systems(iter);
|
||||
});
|
||||
add_archetypes(&mut world, archetype_count);
|
||||
world.clear_entities();
|
||||
|
@ -185,7 +185,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
|||
}
|
||||
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
|
||||
let (mut world, mut schedule) = setup(true, |schedule| {
|
||||
schedule.add_system(for_each);
|
||||
schedule.add_systems(for_each);
|
||||
});
|
||||
add_archetypes(&mut world, archetype_count);
|
||||
world.clear_entities();
|
||||
|
@ -216,7 +216,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
|||
}
|
||||
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
|
||||
let (mut world, mut schedule) = setup(true, |schedule| {
|
||||
schedule.add_system(par_for_each);
|
||||
schedule.add_systems(par_for_each);
|
||||
});
|
||||
add_archetypes(&mut world, archetype_count);
|
||||
world.clear_entities();
|
||||
|
|
|
@ -19,14 +19,9 @@ pub fn run_condition_yes(criterion: &mut Criterion) {
|
|||
fn empty() {}
|
||||
for amount in 0..21 {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(empty.run_if(yes));
|
||||
schedule.add_systems(empty.run_if(yes));
|
||||
for _ in 0..amount {
|
||||
schedule
|
||||
.add_system(empty.run_if(yes))
|
||||
.add_system(empty.run_if(yes))
|
||||
.add_system(empty.run_if(yes))
|
||||
.add_system(empty.run_if(yes))
|
||||
.add_system(empty.run_if(yes));
|
||||
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(yes));
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
|
@ -47,14 +42,9 @@ pub fn run_condition_no(criterion: &mut Criterion) {
|
|||
fn empty() {}
|
||||
for amount in 0..21 {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(empty.run_if(no));
|
||||
schedule.add_systems(empty.run_if(no));
|
||||
for _ in 0..amount {
|
||||
schedule
|
||||
.add_system(empty.run_if(no))
|
||||
.add_system(empty.run_if(no))
|
||||
.add_system(empty.run_if(no))
|
||||
.add_system(empty.run_if(no))
|
||||
.add_system(empty.run_if(no));
|
||||
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(no));
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
|
@ -82,14 +72,11 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) {
|
|||
}
|
||||
for amount in 0..21 {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(empty.run_if(yes_with_query));
|
||||
schedule.add_systems(empty.run_if(yes_with_query));
|
||||
for _ in 0..amount {
|
||||
schedule
|
||||
.add_system(empty.run_if(yes_with_query))
|
||||
.add_system(empty.run_if(yes_with_query))
|
||||
.add_system(empty.run_if(yes_with_query))
|
||||
.add_system(empty.run_if(yes_with_query))
|
||||
.add_system(empty.run_if(yes_with_query));
|
||||
schedule.add_systems(
|
||||
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_query),
|
||||
);
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
|
@ -114,14 +101,11 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) {
|
|||
}
|
||||
for amount in 0..21 {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(empty.run_if(yes_with_resource));
|
||||
schedule.add_systems(empty.run_if(yes_with_resource));
|
||||
for _ in 0..amount {
|
||||
schedule
|
||||
.add_system(empty.run_if(yes_with_resource))
|
||||
.add_system(empty.run_if(yes_with_resource))
|
||||
.add_system(empty.run_if(yes_with_resource))
|
||||
.add_system(empty.run_if(yes_with_resource))
|
||||
.add_system(empty.run_if(yes_with_resource));
|
||||
schedule.add_systems(
|
||||
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_resource),
|
||||
);
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
|
|
|
@ -23,7 +23,7 @@ pub fn empty_systems(criterion: &mut Criterion) {
|
|||
for amount in 0..5 {
|
||||
let mut schedule = Schedule::new();
|
||||
for _ in 0..amount {
|
||||
schedule.add_system(empty);
|
||||
schedule.add_systems(empty);
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(&format!("{:03}_systems", amount), |bencher| {
|
||||
|
@ -35,12 +35,7 @@ pub fn empty_systems(criterion: &mut Criterion) {
|
|||
for amount in 1..21 {
|
||||
let mut schedule = Schedule::new();
|
||||
for _ in 0..amount {
|
||||
schedule
|
||||
.add_system(empty)
|
||||
.add_system(empty)
|
||||
.add_system(empty)
|
||||
.add_system(empty)
|
||||
.add_system(empty);
|
||||
schedule.add_systems((empty, empty, empty, empty, empty));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(&format!("{:03}_systems", 5 * amount), |bencher| {
|
||||
|
@ -79,9 +74,9 @@ pub fn busy_systems(criterion: &mut Criterion) {
|
|||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0))));
|
||||
for system_amount in 0..5 {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(ab).add_system(cd).add_system(ce);
|
||||
schedule.add_systems((ab, cd, ce));
|
||||
for _ in 0..system_amount {
|
||||
schedule.add_system(ab).add_system(cd).add_system(ce);
|
||||
schedule.add_systems((ab, cd, ce));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(
|
||||
|
@ -130,9 +125,9 @@ pub fn contrived(criterion: &mut Criterion) {
|
|||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0))));
|
||||
for system_amount in 0..5 {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(s_0).add_system(s_1).add_system(s_2);
|
||||
schedule.add_systems((s_0, s_1, s_2));
|
||||
for _ in 0..system_amount {
|
||||
schedule.add_system(s_0).add_system(s_1).add_system(s_2);
|
||||
schedule.add_systems((s_0, s_1, s_2));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_app::App;
|
||||
use bevy_app::{App, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use criterion::Criterion;
|
||||
|
||||
|
@ -47,9 +47,7 @@ pub fn schedule(c: &mut Criterion) {
|
|||
world.spawn_batch((0..10000).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0))));
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(ab);
|
||||
schedule.add_system(cd);
|
||||
schedule.add_system(ce);
|
||||
schedule.add_systems((ab, cd, ce));
|
||||
schedule.run(&mut world);
|
||||
|
||||
b.iter(move || schedule.run(&mut world));
|
||||
|
@ -74,7 +72,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
|
|||
group.measurement_time(std::time::Duration::from_secs(15));
|
||||
|
||||
// Method: generate a set of `graph_size` systems which have a One True Ordering.
|
||||
// Add system to the schedule with full constraints. Hopefully this should be maximimally
|
||||
// Add system to the schedule with full constraints. Hopefully this should be maximally
|
||||
// difficult for bevy to figure out.
|
||||
let labels: Vec<_> = (0..1000).map(|i| NumSet(i)).collect();
|
||||
|
||||
|
@ -85,7 +83,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
|
|||
bencher.iter(|| {
|
||||
let mut app = App::new();
|
||||
for _ in 0..graph_size {
|
||||
app.add_system(empty_system);
|
||||
app.add_systems(Update, empty_system);
|
||||
}
|
||||
app.update();
|
||||
});
|
||||
|
@ -95,7 +93,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
|
|||
group.bench_function(format!("{graph_size}_schedule"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
let mut app = App::new();
|
||||
app.add_system(empty_system.in_set(DummySet));
|
||||
app.add_systems(Update, empty_system.in_set(DummySet));
|
||||
|
||||
// Build a fully-connected dependency graph describing the One True Ordering.
|
||||
// Not particularly realistic but this can be refined later.
|
||||
|
@ -107,7 +105,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
|
|||
for label in &labels[i + 1..graph_size] {
|
||||
sys = sys.before(*label);
|
||||
}
|
||||
app.add_system(sys);
|
||||
app.add_systems(Update, sys);
|
||||
}
|
||||
// Run the app for a single frame.
|
||||
// This is necessary since dependency resolution does not occur until the game runs.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
use std::ops::Deref;
|
||||
use std::time::Duration;
|
||||
|
||||
use bevy_app::{App, CoreSet, Plugin};
|
||||
use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_asset::{AddAsset, Assets, Handle};
|
||||
use bevy_core::Name;
|
||||
use bevy_ecs::prelude::*;
|
||||
|
@ -550,10 +550,9 @@ impl Plugin for AnimationPlugin {
|
|||
app.add_asset::<AnimationClip>()
|
||||
.register_asset_reflect::<AnimationClip>()
|
||||
.register_type::<AnimationPlayer>()
|
||||
.add_system(
|
||||
animation_player
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
animation_player.before(TransformSystem::TransformPropagate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
use crate::{
|
||||
CoreSchedule, CoreSet, IntoSystemAppConfig, IntoSystemAppConfigs, Plugin, PluginGroup,
|
||||
StartupSet, SystemAppConfig,
|
||||
First, Main, MainSchedulePlugin, Plugin, PluginGroup, Startup, StateTransition, Update,
|
||||
};
|
||||
pub use bevy_derive::AppLabel;
|
||||
use bevy_ecs::{
|
||||
prelude::*,
|
||||
schedule::{
|
||||
apply_state_transition, common_conditions::run_once as run_once_condition,
|
||||
run_enter_schedule, BoxedScheduleLabel, IntoSystemConfig, IntoSystemSetConfigs,
|
||||
run_enter_schedule, BoxedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs,
|
||||
ScheduleLabel,
|
||||
},
|
||||
};
|
||||
use bevy_utils::{tracing::debug, HashMap, HashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
|
||||
};
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
|
@ -50,7 +52,7 @@ pub(crate) enum AppError {
|
|||
/// #
|
||||
/// fn main() {
|
||||
/// App::new()
|
||||
/// .add_system(hello_world_system)
|
||||
/// .add_systems(Update, hello_world_system)
|
||||
/// .run();
|
||||
/// }
|
||||
///
|
||||
|
@ -71,17 +73,15 @@ pub struct App {
|
|||
pub runner: Box<dyn Fn(App) + Send>, // Send bound is required to make App Send
|
||||
/// The schedule that systems are added to by default.
|
||||
///
|
||||
/// This is initially set to [`CoreSchedule::Main`].
|
||||
pub default_schedule_label: BoxedScheduleLabel,
|
||||
/// The schedule that controls the outer loop of schedule execution.
|
||||
/// The schedule that runs the main loop of schedule execution.
|
||||
///
|
||||
/// This is initially set to [`CoreSchedule::Outer`].
|
||||
pub outer_schedule_label: BoxedScheduleLabel,
|
||||
/// This is initially set to [`Main`].
|
||||
pub main_schedule_label: BoxedScheduleLabel,
|
||||
sub_apps: HashMap<AppLabelId, SubApp>,
|
||||
plugin_registry: Vec<Box<dyn Plugin>>,
|
||||
plugin_name_added: HashSet<String>,
|
||||
/// A private marker to prevent incorrect calls to `App::run()` from `Plugin::build()`
|
||||
is_building_plugin: bool,
|
||||
/// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()`
|
||||
building_plugin_depth: usize,
|
||||
}
|
||||
|
||||
impl Debug for App {
|
||||
|
@ -101,7 +101,7 @@ impl Debug for App {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_app::{App, AppLabel, SubApp, CoreSchedule};
|
||||
/// # use bevy_app::{App, AppLabel, SubApp, Main};
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_ecs::schedule::ScheduleLabel;
|
||||
///
|
||||
|
@ -119,12 +119,10 @@ impl Debug for App {
|
|||
/// // create a app with a resource and a single schedule
|
||||
/// let mut sub_app = App::empty();
|
||||
/// // add an outer schedule that runs the main schedule
|
||||
/// sub_app.add_simple_outer_schedule();
|
||||
/// sub_app.insert_resource(Val(100));
|
||||
///
|
||||
/// // initialize main schedule
|
||||
/// sub_app.init_schedule(CoreSchedule::Main);
|
||||
/// sub_app.add_system(|counter: Res<Val>| {
|
||||
/// sub_app.add_systems(Main, |counter: Res<Val>| {
|
||||
/// // since we assigned the value from the main world in extract
|
||||
/// // we see that value instead of 100
|
||||
/// assert_eq!(counter.0, 10);
|
||||
|
@ -166,7 +164,7 @@ impl SubApp {
|
|||
pub fn run(&mut self) {
|
||||
self.app
|
||||
.world
|
||||
.run_schedule_ref(&*self.app.outer_schedule_label);
|
||||
.run_schedule_ref(&*self.app.main_schedule_label);
|
||||
self.app.world.clear_trackers();
|
||||
}
|
||||
|
||||
|
@ -192,8 +190,7 @@ impl Default for App {
|
|||
#[cfg(feature = "bevy_reflect")]
|
||||
app.init_resource::<AppTypeRegistry>();
|
||||
|
||||
app.add_default_schedules();
|
||||
|
||||
app.add_plugin(MainSchedulePlugin);
|
||||
app.add_event::<AppExit>();
|
||||
|
||||
#[cfg(feature = "bevy_ci_testing")]
|
||||
|
@ -208,8 +205,6 @@ impl Default for App {
|
|||
impl App {
|
||||
/// Creates a new [`App`] with some default structure to enable core engine features.
|
||||
/// This is the preferred constructor for most use cases.
|
||||
///
|
||||
/// This calls [`App::add_default_schedules`].
|
||||
pub fn new() -> App {
|
||||
App::default()
|
||||
}
|
||||
|
@ -226,9 +221,8 @@ impl App {
|
|||
sub_apps: HashMap::default(),
|
||||
plugin_registry: Vec::default(),
|
||||
plugin_name_added: Default::default(),
|
||||
default_schedule_label: Box::new(CoreSchedule::Main),
|
||||
outer_schedule_label: Box::new(CoreSchedule::Outer),
|
||||
is_building_plugin: false,
|
||||
main_schedule_label: Box::new(Main),
|
||||
building_plugin_depth: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,9 +231,8 @@ impl App {
|
|||
/// This method also updates sub apps.
|
||||
/// See [`insert_sub_app`](Self::insert_sub_app) for more details.
|
||||
///
|
||||
/// The schedule run by this method is determined by the [`outer_schedule_label`](App) field.
|
||||
/// In normal usage, this is [`CoreSchedule::Outer`], which will run [`CoreSchedule::Startup`]
|
||||
/// the first time the app is run, then [`CoreSchedule::Main`] on every call of this method.
|
||||
/// The schedule run by this method is determined by the [`main_schedule_label`](App) field.
|
||||
/// By default this is [`Main`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
|
@ -248,7 +241,7 @@ impl App {
|
|||
{
|
||||
#[cfg(feature = "trace")]
|
||||
let _bevy_frame_update_span = info_span!("main app").entered();
|
||||
self.world.run_schedule_ref(&*self.outer_schedule_label);
|
||||
self.world.run_schedule_ref(&*self.main_schedule_label);
|
||||
}
|
||||
for (_label, sub_app) in self.sub_apps.iter_mut() {
|
||||
#[cfg(feature = "trace")]
|
||||
|
@ -291,8 +284,8 @@ impl App {
|
|||
let _bevy_app_run_span = info_span!("bevy_app").entered();
|
||||
|
||||
let mut app = std::mem::replace(self, App::empty());
|
||||
if app.is_building_plugin {
|
||||
panic!("App::run() was called from within Plugin::Build(), which is not allowed.");
|
||||
if app.building_plugin_depth > 0 {
|
||||
panic!("App::run() was called from within Plugin::build(), which is not allowed.");
|
||||
}
|
||||
|
||||
Self::setup(&mut app);
|
||||
|
@ -313,14 +306,14 @@ impl App {
|
|||
}
|
||||
|
||||
/// Adds [`State<S>`] and [`NextState<S>`] resources, [`OnEnter`] and [`OnExit`] schedules
|
||||
/// for each state variant, an instance of [`apply_state_transition::<S>`] in
|
||||
/// [`CoreSet::StateTransitions`] so that transitions happen before [`CoreSet::Update`] and
|
||||
/// a instance of [`run_enter_schedule::<S>`] in [`CoreSet::StateTransitions`] with a
|
||||
/// for each state variant (if they don't already exist), an instance of [`apply_state_transition::<S>`] in
|
||||
/// [`StateTransition`] so that transitions happen before [`Update`] and
|
||||
/// a instance of [`run_enter_schedule::<S>`] in [`StateTransition`] with a
|
||||
/// [`run_once`](`run_once_condition`) condition to run the on enter schedule of the
|
||||
/// initial state.
|
||||
///
|
||||
/// This also adds an [`OnUpdate`] system set for each state variant,
|
||||
/// which runs during [`CoreSet::Update`] after the transitions are applied.
|
||||
/// which runs during [`Update`] after the transitions are applied.
|
||||
/// These system sets only run if the [`State<S>`] resource matches the respective state variant.
|
||||
///
|
||||
/// If you would like to control how other systems run based on the current state,
|
||||
|
@ -329,38 +322,24 @@ impl App {
|
|||
/// Note that you can also apply state transitions at other points in the schedule
|
||||
/// by adding the [`apply_state_transition`] system manually.
|
||||
pub fn add_state<S: States>(&mut self) -> &mut Self {
|
||||
self.init_resource::<State<S>>();
|
||||
self.init_resource::<NextState<S>>();
|
||||
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
|
||||
let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) else {
|
||||
let schedule_label = &self.default_schedule_label;
|
||||
panic!("Default schedule {schedule_label:?} does not exist.")
|
||||
};
|
||||
|
||||
default_schedule.add_systems(
|
||||
(
|
||||
run_enter_schedule::<S>.run_if(run_once_condition()),
|
||||
apply_state_transition::<S>,
|
||||
)
|
||||
.chain()
|
||||
.in_base_set(CoreSet::StateTransitions),
|
||||
);
|
||||
|
||||
for variant in S::variants() {
|
||||
default_schedule.configure_set(
|
||||
OnUpdate(variant.clone())
|
||||
.in_base_set(CoreSet::Update)
|
||||
.run_if(in_state(variant)),
|
||||
self.init_resource::<State<S>>()
|
||||
.init_resource::<NextState<S>>()
|
||||
.add_systems(
|
||||
StateTransition,
|
||||
(
|
||||
run_enter_schedule::<S>.run_if(run_once_condition()),
|
||||
apply_state_transition::<S>,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
|
||||
for variant in S::variants() {
|
||||
self.configure_set(Update, OnUpdate(variant.clone()).run_if(in_state(variant)));
|
||||
}
|
||||
|
||||
// These are different for loops to avoid conflicting access to self
|
||||
for variant in S::variants() {
|
||||
self.add_schedule(OnEnter(variant.clone()), Schedule::new());
|
||||
self.add_schedule(OnExit(variant), Schedule::new());
|
||||
}
|
||||
// The OnEnter, OnExit, and OnTransition schedules are lazily initialized
|
||||
// (i.e. when the first system is added to them), and World::try_run_schedule is used to fail
|
||||
// gracefully if they aren't present.
|
||||
|
||||
self
|
||||
}
|
||||
|
@ -381,28 +360,15 @@ impl App {
|
|||
/// #
|
||||
/// app.add_system(my_system);
|
||||
/// ```
|
||||
pub fn add_system<M>(&mut self, system: impl IntoSystemAppConfig<M>) -> &mut Self {
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
|
||||
let SystemAppConfig { system, schedule } = system.into_app_config();
|
||||
|
||||
if let Some(schedule_label) = schedule {
|
||||
if let Some(schedule) = schedules.get_mut(&*schedule_label) {
|
||||
schedule.add_system(system);
|
||||
} else {
|
||||
panic!("Schedule {schedule_label:?} does not exist.")
|
||||
}
|
||||
} else if let Some(default_schedule) = schedules.get_mut(&*self.default_schedule_label) {
|
||||
default_schedule.add_system(system);
|
||||
} else {
|
||||
let schedule_label = &self.default_schedule_label;
|
||||
panic!("Default schedule {schedule_label:?} does not exist.")
|
||||
}
|
||||
|
||||
self
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Update, your_system).`"
|
||||
)]
|
||||
pub fn add_system<M>(&mut self, system: impl IntoSystemConfigs<M>) -> &mut Self {
|
||||
self.add_systems(Update, system)
|
||||
}
|
||||
|
||||
/// Adds a system to the default system set and schedule of the app's [`Schedules`].
|
||||
/// Adds a system to the given schedule in this app's [`Schedules`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -415,36 +381,27 @@ impl App {
|
|||
/// # fn system_b() {}
|
||||
/// # fn system_c() {}
|
||||
/// #
|
||||
/// app.add_systems((system_a, system_b, system_c));
|
||||
/// app.add_systems(Update, (system_a, system_b, system_c));
|
||||
/// ```
|
||||
pub fn add_systems<M>(&mut self, systems: impl IntoSystemAppConfigs<M>) -> &mut Self {
|
||||
pub fn add_systems<M>(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
systems: impl IntoSystemConfigs<M>,
|
||||
) -> &mut Self {
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
|
||||
match systems.into_app_configs().0 {
|
||||
crate::InnerConfigs::Blanket { systems, schedule } => {
|
||||
let schedule = if let Some(label) = schedule {
|
||||
schedules
|
||||
.get_mut(&*label)
|
||||
.unwrap_or_else(|| panic!("Schedule '{label:?}' does not exist."))
|
||||
} else {
|
||||
let label = &*self.default_schedule_label;
|
||||
schedules
|
||||
.get_mut(label)
|
||||
.unwrap_or_else(|| panic!("Default schedule '{label:?}' does not exist."))
|
||||
};
|
||||
schedule.add_systems(systems);
|
||||
}
|
||||
crate::InnerConfigs::Granular(systems) => {
|
||||
for system in systems {
|
||||
self.add_system(system);
|
||||
}
|
||||
}
|
||||
if let Some(schedule) = schedules.get_mut(&schedule) {
|
||||
schedule.add_systems(systems);
|
||||
} else {
|
||||
let mut new_schedule = Schedule::new();
|
||||
new_schedule.add_systems(systems);
|
||||
schedules.insert(schedule, new_schedule);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a system to [`CoreSchedule::Startup`].
|
||||
/// Adds a system to [`Startup`].
|
||||
///
|
||||
/// These systems will run exactly once, at the start of the [`App`]'s lifecycle.
|
||||
/// To add a system that runs every frame, see [`add_system`](Self::add_system).
|
||||
|
@ -460,13 +417,17 @@ impl App {
|
|||
/// }
|
||||
///
|
||||
/// App::new()
|
||||
/// .add_startup_system(my_startup_system);
|
||||
/// .add_systems(Startup, my_startup_system);
|
||||
/// ```
|
||||
pub fn add_startup_system<M>(&mut self, system: impl IntoSystemConfig<M>) -> &mut Self {
|
||||
self.add_system(system.in_schedule(CoreSchedule::Startup))
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Startup, your_system).`"
|
||||
)]
|
||||
pub fn add_startup_system<M>(&mut self, system: impl IntoSystemConfigs<M>) -> &mut Self {
|
||||
self.add_systems(Startup, system)
|
||||
}
|
||||
|
||||
/// Adds a collection of systems to [`CoreSchedule::Startup`].
|
||||
/// Adds a collection of systems to [`Startup`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
@ -479,88 +440,58 @@ impl App {
|
|||
/// # fn startup_system_b() {}
|
||||
/// # fn startup_system_c() {}
|
||||
/// #
|
||||
/// app.add_startup_systems((
|
||||
/// app.add_systems(Startup, (
|
||||
/// startup_system_a,
|
||||
/// startup_system_b,
|
||||
/// startup_system_c,
|
||||
/// ));
|
||||
/// ```
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "Please use `add_systems` instead. If you didn't change the default base set, you should use `add_systems(Startup, your_system).`"
|
||||
)]
|
||||
pub fn add_startup_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) -> &mut Self {
|
||||
self.add_systems(systems.into_configs().in_schedule(CoreSchedule::Startup))
|
||||
self.add_systems(Startup, systems.into_configs())
|
||||
}
|
||||
|
||||
/// Configures a system set in the default schedule, adding the set if it does not exist.
|
||||
pub fn configure_set(&mut self, set: impl IntoSystemSetConfig) -> &mut Self {
|
||||
self.world
|
||||
.resource_mut::<Schedules>()
|
||||
.get_mut(&*self.default_schedule_label)
|
||||
.unwrap()
|
||||
.configure_set(set);
|
||||
pub fn configure_set(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
set: impl IntoSystemSetConfig,
|
||||
) -> &mut Self {
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
if let Some(schedule) = schedules.get_mut(&schedule) {
|
||||
schedule.configure_set(set);
|
||||
} else {
|
||||
let mut new_schedule = Schedule::new();
|
||||
new_schedule.configure_set(set);
|
||||
schedules.insert(schedule, new_schedule);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures a collection of system sets in the default schedule, adding any sets that do not exist.
|
||||
pub fn configure_sets(&mut self, sets: impl IntoSystemSetConfigs) -> &mut Self {
|
||||
self.world
|
||||
.resource_mut::<Schedules>()
|
||||
.get_mut(&*self.default_schedule_label)
|
||||
.unwrap()
|
||||
.configure_sets(sets);
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds standardized schedules and labels to an [`App`].
|
||||
///
|
||||
/// Adding these schedules is necessary to make almost all core engine features work.
|
||||
/// This is typically done implicitly by calling `App::default`, which is in turn called by
|
||||
/// [`App::new`].
|
||||
///
|
||||
/// The schedules added are defined in the [`CoreSchedule`] enum,
|
||||
/// and have a starting configuration defined by:
|
||||
///
|
||||
/// - [`CoreSchedule::Outer`]: uses [`CoreSchedule::outer_schedule`]
|
||||
/// - [`CoreSchedule::Startup`]: uses [`StartupSet::base_schedule`]
|
||||
/// - [`CoreSchedule::Main`]: uses [`CoreSet::base_schedule`]
|
||||
/// - [`CoreSchedule::FixedUpdate`]: no starting configuration
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_app::App;
|
||||
/// use bevy_ecs::schedule::Schedules;
|
||||
///
|
||||
/// let app = App::empty()
|
||||
/// .init_resource::<Schedules>()
|
||||
/// .add_default_schedules()
|
||||
/// .update();
|
||||
/// ```
|
||||
pub fn add_default_schedules(&mut self) -> &mut Self {
|
||||
self.add_schedule(CoreSchedule::Outer, CoreSchedule::outer_schedule());
|
||||
self.add_schedule(CoreSchedule::Startup, StartupSet::base_schedule());
|
||||
self.add_schedule(CoreSchedule::Main, CoreSet::base_schedule());
|
||||
self.init_schedule(CoreSchedule::FixedUpdate);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// adds a single threaded outer schedule to the [`App`] that just runs the main schedule
|
||||
pub fn add_simple_outer_schedule(&mut self) -> &mut Self {
|
||||
fn run_main_schedule(world: &mut World) {
|
||||
world.run_schedule(CoreSchedule::Main);
|
||||
pub fn configure_sets(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
sets: impl IntoSystemSetConfigs,
|
||||
) -> &mut Self {
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
if let Some(schedule) = schedules.get_mut(&schedule) {
|
||||
schedule.configure_sets(sets);
|
||||
} else {
|
||||
let mut new_schedule = Schedule::new();
|
||||
new_schedule.configure_sets(sets);
|
||||
schedules.insert(schedule, new_schedule);
|
||||
}
|
||||
|
||||
self.edit_schedule(CoreSchedule::Outer, |schedule| {
|
||||
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
|
||||
schedule.add_system(run_main_schedule);
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Setup the application to manage events of type `T`.
|
||||
///
|
||||
/// This is done by adding a [`Resource`] of type [`Events::<T>`],
|
||||
/// and inserting an [`update_system`](Events::update_system) into [`CoreSet::First`].
|
||||
/// and inserting an [`update_system`](Events::update_system) into [`First`].
|
||||
///
|
||||
/// See [`Events`] for defining events.
|
||||
///
|
||||
|
@ -581,7 +512,7 @@ impl App {
|
|||
{
|
||||
if !self.world.contains_resource::<Events<T>>() {
|
||||
self.init_resource::<Events<T>>()
|
||||
.add_system(Events::<T>::update_system.in_base_set(CoreSet::First));
|
||||
.add_systems(First, Events::<T>::update_system);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
@ -760,9 +691,12 @@ impl App {
|
|||
plugin_name: plugin.name().to_string(),
|
||||
})?;
|
||||
}
|
||||
self.is_building_plugin = true;
|
||||
plugin.build(self);
|
||||
self.is_building_plugin = false;
|
||||
self.building_plugin_depth += 1;
|
||||
let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self)));
|
||||
self.building_plugin_depth -= 1;
|
||||
if let Err(payload) = result {
|
||||
resume_unwind(payload);
|
||||
}
|
||||
self.plugin_registry.push(plugin);
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -1014,6 +948,11 @@ pub struct AppExit;
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_ecs::{
|
||||
schedule::{OnEnter, States},
|
||||
system::Commands,
|
||||
};
|
||||
|
||||
use crate::{App, Plugin};
|
||||
|
||||
struct PluginA;
|
||||
|
@ -1061,11 +1000,48 @@ mod tests {
|
|||
#[should_panic]
|
||||
fn cant_call_app_run_from_plugin_build() {
|
||||
struct PluginRun;
|
||||
struct InnerPlugin;
|
||||
impl Plugin for InnerPlugin {
|
||||
fn build(&self, _: &mut crate::App) {}
|
||||
}
|
||||
impl Plugin for PluginRun {
|
||||
fn build(&self, app: &mut crate::App) {
|
||||
app.run();
|
||||
app.add_plugin(InnerPlugin).run();
|
||||
}
|
||||
}
|
||||
App::new().add_plugin(PluginRun);
|
||||
}
|
||||
|
||||
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
|
||||
enum AppState {
|
||||
#[default]
|
||||
MainMenu,
|
||||
}
|
||||
fn bar(mut commands: Commands) {
|
||||
commands.spawn_empty();
|
||||
}
|
||||
|
||||
fn foo(mut commands: Commands) {
|
||||
commands.spawn_empty();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_systems_should_create_schedule_if_it_does_not_exist() {
|
||||
let mut app = App::new();
|
||||
app.add_state::<AppState>()
|
||||
.add_systems(OnEnter(AppState::MainMenu), (foo, bar));
|
||||
|
||||
app.world.run_schedule(OnEnter(AppState::MainMenu));
|
||||
assert_eq!(app.world.entities().len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_systems_should_create_schedule_if_it_does_not_exist2() {
|
||||
let mut app = App::new();
|
||||
app.add_systems(OnEnter(AppState::MainMenu), (foo, bar))
|
||||
.add_state::<AppState>();
|
||||
|
||||
app.world.run_schedule(OnEnter(AppState::MainMenu));
|
||||
assert_eq!(app.world.entities().len(), 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{app::AppExit, App};
|
||||
use crate::{app::AppExit, App, Update};
|
||||
use serde::Deserialize;
|
||||
|
||||
use bevy_ecs::prelude::Resource;
|
||||
|
@ -47,7 +47,7 @@ pub(crate) fn setup_app(app: &mut App) -> &mut App {
|
|||
};
|
||||
|
||||
app.insert_resource(config)
|
||||
.add_system(ci_testing_exit_after);
|
||||
.add_systems(Update, ci_testing_exit_after);
|
||||
|
||||
app
|
||||
}
|
||||
|
|
|
@ -1,291 +0,0 @@
|
|||
use bevy_ecs::{
|
||||
all_tuples,
|
||||
schedule::{
|
||||
BaseSystemSet, BoxedScheduleLabel, Condition, FreeSystemSet, IntoSystemConfig,
|
||||
IntoSystemSet, ScheduleLabel, SystemConfig, SystemConfigs,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::CoreSchedule;
|
||||
|
||||
/// A [`System`] with [`App`]-aware scheduling metadata.
|
||||
///
|
||||
/// [`System`]: bevy_ecs::prelude::System
|
||||
/// [`App`]: crate::App
|
||||
pub struct SystemAppConfig {
|
||||
pub(crate) system: SystemConfig,
|
||||
pub(crate) schedule: Option<BoxedScheduleLabel>,
|
||||
}
|
||||
|
||||
/// Types that can be converted into a [`SystemAppConfig`].
|
||||
///
|
||||
/// This has been implemented for all `System<In = (), Out = ()>` trait objects
|
||||
/// and all functions that convert into such.
|
||||
pub trait IntoSystemAppConfig<Marker>: Sized {
|
||||
/// Converts into a [`SystemAppConfig`].
|
||||
fn into_app_config(self) -> SystemAppConfig;
|
||||
|
||||
/// Adds the system to the provided `schedule`.
|
||||
///
|
||||
/// If a schedule is not specified, it will be added to the [`App`]'s default schedule.
|
||||
///
|
||||
/// [`App`]: crate::App
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the system has already been assigned to a schedule.
|
||||
#[track_caller]
|
||||
fn in_schedule(self, schedule: impl ScheduleLabel) -> SystemAppConfig {
|
||||
let mut config = self.into_app_config();
|
||||
if let Some(old_schedule) = &config.schedule {
|
||||
panic!(
|
||||
"Cannot add system to schedule '{schedule:?}': it is already in '{old_schedule:?}'."
|
||||
);
|
||||
}
|
||||
config.schedule = Some(Box::new(schedule));
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
/// Adds the system to [`CoreSchedule::Startup`].
|
||||
/// This is a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
|
||||
///
|
||||
/// Systems in this schedule will run exactly once, at the start of the [`App`]'s lifecycle.
|
||||
///
|
||||
/// [`App`]: crate::App
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// fn my_startup_system(_commands: Commands) {
|
||||
/// println!("My startup system");
|
||||
/// }
|
||||
///
|
||||
/// App::new()
|
||||
/// .add_system(my_startup_system.on_startup())
|
||||
/// .run();
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the system has already been assigned to a schedule.
|
||||
#[inline]
|
||||
fn on_startup(self) -> SystemAppConfig {
|
||||
self.in_schedule(CoreSchedule::Startup)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfig<(), Self> for SystemAppConfig {
|
||||
fn into_config(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_set(self, set: impl FreeSystemSet) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.in_set(set),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_base_set(self, set: impl BaseSystemSet) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.in_base_set(set),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
fn no_default_base_set(self) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.no_default_base_set(),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.before(set),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
fn after<M>(self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.after(set),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
fn run_if<P>(self, condition: impl Condition<P>) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.run_if(condition),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.ambiguous_with(set),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
|
||||
fn ambiguous_with_all(self) -> Self {
|
||||
let Self { system, schedule } = self;
|
||||
Self {
|
||||
system: system.ambiguous_with_all(),
|
||||
schedule,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemAppConfig<()> for SystemAppConfig {
|
||||
fn into_app_config(self) -> SystemAppConfig {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<Marker, T> IntoSystemAppConfig<Marker> for T
|
||||
where
|
||||
T: IntoSystemConfig<Marker>,
|
||||
{
|
||||
fn into_app_config(self) -> SystemAppConfig {
|
||||
SystemAppConfig {
|
||||
system: self.into_config(),
|
||||
schedule: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`SystemAppConfig`]s.
|
||||
pub struct SystemAppConfigs(pub(crate) InnerConfigs);
|
||||
|
||||
pub(crate) enum InnerConfigs {
|
||||
/// This came from an instance of `SystemConfigs`.
|
||||
/// All systems are in the same schedule.
|
||||
Blanket {
|
||||
systems: SystemConfigs,
|
||||
schedule: Option<BoxedScheduleLabel>,
|
||||
},
|
||||
/// This came from several separate instances of `SystemAppConfig`.
|
||||
/// Each system gets its own schedule.
|
||||
Granular(Vec<SystemAppConfig>),
|
||||
}
|
||||
|
||||
/// Types that can convert into [`SystemAppConfigs`].
|
||||
pub trait IntoSystemAppConfigs<Marker>: Sized {
|
||||
/// Converts to [`SystemAppConfigs`].
|
||||
fn into_app_configs(self) -> SystemAppConfigs;
|
||||
|
||||
/// Adds the systems to the provided `schedule`.
|
||||
///
|
||||
/// If a schedule is not specified, they will be added to the [`App`]'s default schedule.
|
||||
///
|
||||
/// [`App`]: crate::App
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If any of the systems have already been assigned to a schedule.
|
||||
#[track_caller]
|
||||
fn in_schedule(self, label: impl ScheduleLabel) -> SystemAppConfigs {
|
||||
let mut configs = self.into_app_configs();
|
||||
|
||||
match &mut configs.0 {
|
||||
InnerConfigs::Blanket { schedule, .. } => {
|
||||
if schedule.is_some() {
|
||||
panic!(
|
||||
"Cannot add systems to the schedule '{label:?}: they are already in '{schedule:?}'"
|
||||
);
|
||||
}
|
||||
*schedule = Some(Box::new(label));
|
||||
}
|
||||
InnerConfigs::Granular(configs) => {
|
||||
for SystemAppConfig { schedule, .. } in configs {
|
||||
if schedule.is_some() {
|
||||
panic!(
|
||||
"Cannot add system to the schedule '{label:?}': it is already in '{schedule:?}'."
|
||||
);
|
||||
}
|
||||
*schedule = Some(label.dyn_clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configs
|
||||
}
|
||||
|
||||
/// Adds the systems to [`CoreSchedule::Startup`].
|
||||
/// This is a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # let mut app = App::new();
|
||||
/// # fn startup_system_a() {}
|
||||
/// # fn startup_system_b() {}
|
||||
/// # fn startup_system_c() {}
|
||||
/// #
|
||||
/// app.add_systems(
|
||||
/// (
|
||||
/// startup_system_a,
|
||||
/// startup_system_b,
|
||||
/// startup_system_c,
|
||||
/// )
|
||||
/// .on_startup()
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If any of the systems have already been assigned to a schedule.
|
||||
#[track_caller]
|
||||
fn on_startup(self) -> SystemAppConfigs {
|
||||
self.in_schedule(CoreSchedule::Startup)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemAppConfigs<()> for SystemAppConfigs {
|
||||
fn into_app_configs(self) -> SystemAppConfigs {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemAppConfigs<()> for SystemConfigs {
|
||||
fn into_app_configs(self) -> SystemAppConfigs {
|
||||
SystemAppConfigs(InnerConfigs::Blanket {
|
||||
systems: self,
|
||||
schedule: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_system_collection {
|
||||
($(($param: ident, $sys: ident)),*) => {
|
||||
impl<$($param, $sys),*> IntoSystemAppConfigs<($($param,)*)> for ($($sys,)*)
|
||||
where
|
||||
$($sys: IntoSystemAppConfig<$param>),*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
fn into_app_configs(self) -> SystemAppConfigs {
|
||||
let ($($sys,)*) = self;
|
||||
SystemAppConfigs(InnerConfigs::Granular(vec![$($sys.into_app_config(),)*]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_tuples!(impl_system_collection, 0, 15, P, S);
|
|
@ -3,7 +3,7 @@
|
|||
#![warn(missing_docs)]
|
||||
|
||||
mod app;
|
||||
mod config;
|
||||
mod main_schedule;
|
||||
mod plugin;
|
||||
mod plugin_group;
|
||||
mod schedule_runner;
|
||||
|
@ -13,7 +13,7 @@ mod ci_testing;
|
|||
|
||||
pub use app::*;
|
||||
pub use bevy_derive::DynamicPlugin;
|
||||
pub use config::*;
|
||||
pub use main_schedule::*;
|
||||
pub use plugin::*;
|
||||
pub use plugin_group::*;
|
||||
pub use schedule_runner::*;
|
||||
|
@ -26,185 +26,10 @@ pub mod prelude {
|
|||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
app::App,
|
||||
config::{IntoSystemAppConfig, IntoSystemAppConfigs},
|
||||
CoreSchedule, CoreSet, DynamicPlugin, Plugin, PluginGroup, StartupSet,
|
||||
main_schedule::{
|
||||
First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate,
|
||||
Startup, StateTransition, Update,
|
||||
},
|
||||
DynamicPlugin, Plugin, PluginGroup,
|
||||
};
|
||||
}
|
||||
|
||||
use bevy_ecs::{
|
||||
schedule::{
|
||||
apply_system_buffers, IntoSystemConfig, IntoSystemSetConfig, IntoSystemSetConfigs,
|
||||
Schedule, ScheduleLabel, SystemSet,
|
||||
},
|
||||
system::Local,
|
||||
world::World,
|
||||
};
|
||||
|
||||
/// The names of the default [`App`] schedules.
|
||||
///
|
||||
/// The corresponding [`Schedule`](bevy_ecs::schedule::Schedule) objects are added by [`App::add_default_schedules`].
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum CoreSchedule {
|
||||
/// The schedule that runs once when the app starts.
|
||||
Startup,
|
||||
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
|
||||
Main,
|
||||
/// The schedule that controls which schedules run.
|
||||
///
|
||||
/// This is typically created using the [`CoreSchedule::outer_schedule`] method,
|
||||
/// and does not need to manipulated during ordinary use.
|
||||
Outer,
|
||||
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
|
||||
///
|
||||
/// The exclusive `run_fixed_update_schedule` system runs this schedule during the [`CoreSet::FixedUpdate`] system set.
|
||||
FixedUpdate,
|
||||
}
|
||||
|
||||
impl CoreSchedule {
|
||||
/// An exclusive system that controls which schedule should be running.
|
||||
///
|
||||
/// [`CoreSchedule::Main`] is always run.
|
||||
///
|
||||
/// If this is the first time this system has been run, [`CoreSchedule::Startup`] will run before [`CoreSchedule::Main`].
|
||||
pub fn outer_loop(world: &mut World, mut run_at_least_once: Local<bool>) {
|
||||
if !*run_at_least_once {
|
||||
world.run_schedule(CoreSchedule::Startup);
|
||||
*run_at_least_once = true;
|
||||
}
|
||||
|
||||
world.run_schedule(CoreSchedule::Main);
|
||||
}
|
||||
|
||||
/// Initializes a single threaded schedule for [`CoreSchedule::Outer`] that contains the [`outer_loop`](CoreSchedule::outer_loop) system.
|
||||
pub fn outer_schedule() -> Schedule {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::SingleThreaded);
|
||||
schedule.add_system(Self::outer_loop);
|
||||
schedule
|
||||
}
|
||||
}
|
||||
|
||||
/// The names of the default [`App`] system sets.
|
||||
///
|
||||
/// These are ordered in the same order they are listed.
|
||||
///
|
||||
/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_schedules`].
|
||||
///
|
||||
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
|
||||
/// that runs immediately after the matching system set.
|
||||
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
#[system_set(base)]
|
||||
pub enum CoreSet {
|
||||
/// Runs before all other members of this set.
|
||||
First,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `First`.
|
||||
FirstFlush,
|
||||
/// Runs before [`CoreSet::Update`].
|
||||
PreUpdate,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `PreUpdate`.
|
||||
PreUpdateFlush,
|
||||
/// Applies [`State`](bevy_ecs::schedule::State) transitions
|
||||
StateTransitions,
|
||||
/// Runs systems that should only occur after a fixed period of time.
|
||||
///
|
||||
/// The `run_fixed_update_schedule` system runs the [`CoreSchedule::FixedUpdate`] system in this system set.
|
||||
FixedUpdate,
|
||||
/// Responsible for doing most app logic. Systems should be registered here by default.
|
||||
Update,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `Update`.
|
||||
UpdateFlush,
|
||||
/// Runs after [`CoreSet::Update`].
|
||||
PostUpdate,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `PostUpdate`.
|
||||
PostUpdateFlush,
|
||||
/// Runs after all other members of this set.
|
||||
Last,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `Last`.
|
||||
LastFlush,
|
||||
}
|
||||
|
||||
impl CoreSet {
|
||||
/// Sets up the base structure of [`CoreSchedule::Main`].
|
||||
///
|
||||
/// The sets defined in this enum are configured to run in order,
|
||||
/// and a copy of [`apply_system_buffers`] is inserted into each `*Flush` set.
|
||||
pub fn base_schedule() -> Schedule {
|
||||
use CoreSet::*;
|
||||
let mut schedule = Schedule::new();
|
||||
|
||||
// Create "stage-like" structure using buffer flushes + ordering
|
||||
schedule
|
||||
.set_default_base_set(Update)
|
||||
.add_system(apply_system_buffers.in_base_set(FirstFlush))
|
||||
.add_system(apply_system_buffers.in_base_set(PreUpdateFlush))
|
||||
.add_system(apply_system_buffers.in_base_set(UpdateFlush))
|
||||
.add_system(apply_system_buffers.in_base_set(PostUpdateFlush))
|
||||
.add_system(apply_system_buffers.in_base_set(LastFlush))
|
||||
.configure_sets(
|
||||
(
|
||||
First,
|
||||
FirstFlush,
|
||||
PreUpdate,
|
||||
PreUpdateFlush,
|
||||
StateTransitions,
|
||||
FixedUpdate,
|
||||
Update,
|
||||
UpdateFlush,
|
||||
PostUpdate,
|
||||
PostUpdateFlush,
|
||||
Last,
|
||||
LastFlush,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
schedule
|
||||
}
|
||||
}
|
||||
|
||||
/// The names of the default [`App`] startup sets, which live in [`CoreSchedule::Startup`].
|
||||
///
|
||||
/// The corresponding [`SystemSets`](bevy_ecs::schedule::SystemSet) are added by [`App::add_default_schedules`].
|
||||
///
|
||||
/// The `*Flush` sets are assigned to the copy of [`apply_system_buffers`]
|
||||
/// that runs immediately after the matching system set.
|
||||
/// These can be useful for ordering, but you almost never want to add your systems to these sets.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
#[system_set(base)]
|
||||
pub enum StartupSet {
|
||||
/// Runs once before [`StartupSet::Startup`].
|
||||
PreStartup,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `PreStartup`.
|
||||
PreStartupFlush,
|
||||
/// Runs once when an [`App`] starts up.
|
||||
Startup,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `Startup`.
|
||||
StartupFlush,
|
||||
/// Runs once after [`StartupSet::Startup`].
|
||||
PostStartup,
|
||||
/// The copy of [`apply_system_buffers`] that runs immediately after `PostStartup`.
|
||||
PostStartupFlush,
|
||||
}
|
||||
|
||||
impl StartupSet {
|
||||
/// Sets up the base structure of [`CoreSchedule::Startup`].
|
||||
///
|
||||
/// The sets defined in this enum are configured to run in order,
|
||||
/// and a copy of [`apply_system_buffers`] is inserted into each `*Flush` set.
|
||||
pub fn base_schedule() -> Schedule {
|
||||
use StartupSet::*;
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.set_default_base_set(Startup);
|
||||
|
||||
// Create "stage-like" structure using buffer flushes + ordering
|
||||
schedule.add_system(apply_system_buffers.in_base_set(PreStartupFlush));
|
||||
schedule.add_system(apply_system_buffers.in_base_set(StartupFlush));
|
||||
schedule.add_system(apply_system_buffers.in_base_set(PostStartupFlush));
|
||||
|
||||
schedule.configure_set(PreStartup.before(PreStartupFlush));
|
||||
schedule.configure_set(Startup.after(PreStartupFlush).before(StartupFlush));
|
||||
schedule.configure_set(PostStartup.after(StartupFlush).before(PostStartupFlush));
|
||||
|
||||
schedule
|
||||
}
|
||||
}
|
||||
|
|
168
crates/bevy_app/src/main_schedule.rs
Normal file
168
crates/bevy_app/src/main_schedule.rs
Normal file
|
@ -0,0 +1,168 @@
|
|||
use crate::{App, Plugin};
|
||||
use bevy_ecs::{
|
||||
schedule::{ExecutorKind, Schedule, ScheduleLabel},
|
||||
system::{Local, Resource},
|
||||
world::{Mut, World},
|
||||
};
|
||||
|
||||
/// The schedule that contains the app logic that is evaluated each tick of [`App::update()`].
|
||||
///
|
||||
/// By default, it will run the following schedules in the given order:
|
||||
///
|
||||
/// On the first run of the schedule (and only on the first run), it will run:
|
||||
/// * [`PreStartup`]
|
||||
/// * [`Startup`]
|
||||
/// * [`PostStartup`]
|
||||
///
|
||||
/// Then it will run:
|
||||
/// * [`First`]
|
||||
/// * [`PreUpdate`]
|
||||
/// * [`StateTransition`]
|
||||
/// * [`RunFixedUpdateLoop`]
|
||||
/// * This will run [`FixedUpdate`] zero to many times, based on how much time has elapsed.
|
||||
/// * [`Update`]
|
||||
/// * [`PostUpdate`]
|
||||
/// * [`Last`]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Main;
|
||||
|
||||
/// The schedule that runs before [`Startup`].
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PreStartup;
|
||||
|
||||
/// The schedule that runs once when the app starts.
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Startup;
|
||||
|
||||
/// The schedule that runs once after [`Startup`].
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PostStartup;
|
||||
|
||||
/// Runs first in the schedule.
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct First;
|
||||
|
||||
/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard
|
||||
/// input OS events into an `Events` resource. This enables systems in [`Update`] to consume the events from the `Events`
|
||||
/// resource without actually knowing about (or taking a direct scheduler dependency on) the "os-level keyboard event sytsem".
|
||||
///
|
||||
/// [`PreUpdate`] exists to do "engine/plugin preparation work" that ensures the APIs consumed in [`Update`] are "ready".
|
||||
/// [`PreUpdate`] abstracts out "pre work implementation details".
|
||||
///
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PreUpdate;
|
||||
|
||||
/// Runs [state transitions](bevy_ecs::schedule::States).
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct StateTransition;
|
||||
|
||||
/// Runs the [`FixedUpdate`] schedule in a loop according until all relevant elapsed time has been "consumed".
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RunFixedUpdateLoop;
|
||||
|
||||
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
|
||||
///
|
||||
/// The exclusive `run_fixed_update_schedule` system runs this schedule.
|
||||
/// This is run by the [`RunFixedUpdateLoop`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct FixedUpdate;
|
||||
|
||||
/// The schedule that contains app logic.
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Update;
|
||||
|
||||
/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy
|
||||
/// to "global" absolute transforms. This enables the [`PostUpdate`] transform-sync system to react to "local transform" changes in
|
||||
/// [`Update`] without the [`Update`] systems needing to know about (or add scheduler dependencies for) the "global transform sync system".
|
||||
///
|
||||
/// [`PostUpdate`] exists to do "engine/plugin response work" to things that happened in [`Update`].
|
||||
/// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`].
|
||||
///
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PostUpdate;
|
||||
|
||||
/// Runs last in the schedule.
|
||||
/// This is run by the [`Main`] schedule.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Last;
|
||||
|
||||
/// Defines the schedules to be run for the [`Main`] schedule, including
|
||||
/// their order.
|
||||
#[derive(Resource, Debug)]
|
||||
pub struct MainScheduleOrder {
|
||||
/// The labels to run for the [`Main`] schedule (in the order they will be run).
|
||||
pub labels: Vec<Box<dyn ScheduleLabel>>,
|
||||
}
|
||||
|
||||
impl Default for MainScheduleOrder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
labels: vec![
|
||||
Box::new(First),
|
||||
Box::new(PreUpdate),
|
||||
Box::new(StateTransition),
|
||||
Box::new(RunFixedUpdateLoop),
|
||||
Box::new(Update),
|
||||
Box::new(PostUpdate),
|
||||
Box::new(Last),
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MainScheduleOrder {
|
||||
/// Adds the given `schedule` after the `after` schedule
|
||||
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
|
||||
let index = self
|
||||
.labels
|
||||
.iter()
|
||||
.position(|current| (**current).eq(&after))
|
||||
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
|
||||
self.labels.insert(index + 1, Box::new(schedule));
|
||||
}
|
||||
}
|
||||
|
||||
impl Main {
|
||||
/// A system that runs the "main schedule"
|
||||
pub fn run_main(world: &mut World, mut run_at_least_once: Local<bool>) {
|
||||
if !*run_at_least_once {
|
||||
let _ = world.try_run_schedule(PreStartup);
|
||||
let _ = world.try_run_schedule(Startup);
|
||||
let _ = world.try_run_schedule(PostStartup);
|
||||
*run_at_least_once = true;
|
||||
}
|
||||
|
||||
world.resource_scope(|world, order: Mut<MainScheduleOrder>| {
|
||||
for label in &order.labels {
|
||||
let _ = world.try_run_schedule_ref(&**label);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`].
|
||||
pub struct MainSchedulePlugin;
|
||||
|
||||
impl Plugin for MainSchedulePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// simple "facilitator" schedules benefit from simpler single threaded scheduling
|
||||
let mut main_schedule = Schedule::new();
|
||||
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
|
||||
let mut fixed_update_loop_schedule = Schedule::new();
|
||||
fixed_update_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
|
||||
|
||||
app.add_schedule(Main, main_schedule)
|
||||
.add_schedule(RunFixedUpdateLoop, fixed_update_loop_schedule)
|
||||
.init_resource::<MainScheduleOrder>()
|
||||
.add_systems(Main, Main::run_main);
|
||||
}
|
||||
}
|
|
@ -644,7 +644,7 @@ pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
|
|||
mod test {
|
||||
use super::*;
|
||||
use crate::{loader::LoadedAsset, update_asset_storage_system};
|
||||
use bevy_app::App;
|
||||
use bevy_app::{App, Update};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_utils::BoxedFuture;
|
||||
|
@ -852,8 +852,13 @@ mod test {
|
|||
let mut app = App::new();
|
||||
app.insert_resource(assets);
|
||||
app.insert_resource(asset_server);
|
||||
app.add_system(free_unused_assets_system.in_set(FreeUnusedAssets));
|
||||
app.add_system(update_asset_storage_system::<PngAsset>.after(FreeUnusedAssets));
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
free_unused_assets_system.in_set(FreeUnusedAssets),
|
||||
update_asset_storage_system::<PngAsset>.after(FreeUnusedAssets),
|
||||
),
|
||||
);
|
||||
|
||||
fn load_asset(path: AssetPath, world: &World) -> HandleUntyped {
|
||||
let asset_server = world.resource::<AssetServer>();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
update_asset_storage_system, Asset, AssetLoader, AssetServer, AssetSet, Handle, HandleId,
|
||||
RefChange, ReflectAsset, ReflectHandle,
|
||||
update_asset_storage_system, Asset, AssetEvents, AssetLoader, AssetServer, Handle, HandleId,
|
||||
LoadAssets, RefChange, ReflectAsset, ReflectHandle,
|
||||
};
|
||||
use bevy_app::{App, AppTypeRegistry};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
@ -331,8 +331,8 @@ impl AddAsset for App {
|
|||
};
|
||||
|
||||
self.insert_resource(assets)
|
||||
.add_system(Assets::<T>::asset_event_system.in_base_set(AssetSet::AssetEvents))
|
||||
.add_system(update_asset_storage_system::<T>.in_base_set(AssetSet::LoadAssets))
|
||||
.add_systems(LoadAssets, update_asset_storage_system::<T>)
|
||||
.add_systems(AssetEvents, Assets::<T>::asset_event_system)
|
||||
.register_type::<Handle<T>>()
|
||||
.add_event::<AssetEvent<T>>()
|
||||
}
|
||||
|
@ -360,9 +360,9 @@ impl AddAsset for App {
|
|||
{
|
||||
#[cfg(feature = "debug_asset_server")]
|
||||
{
|
||||
self.add_system(
|
||||
crate::debug_asset_server::sync_debug_assets::<T>
|
||||
.in_base_set(bevy_app::CoreSet::Update),
|
||||
self.add_systems(
|
||||
bevy_app::Update,
|
||||
crate::debug_asset_server::sync_debug_assets::<T>,
|
||||
);
|
||||
let mut app = self
|
||||
.world
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! Internal assets (e.g. shaders) are bundled directly into an application and can't be hot
|
||||
//! reloaded using the conventional API.
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_app::{App, Plugin, Update};
|
||||
use bevy_ecs::{prelude::*, system::SystemState};
|
||||
use bevy_tasks::{IoTaskPool, TaskPoolBuilder};
|
||||
use bevy_utils::HashMap;
|
||||
|
@ -75,7 +75,7 @@ impl Plugin for DebugAssetServerPlugin {
|
|||
watch_for_changes: true,
|
||||
});
|
||||
app.insert_non_send_resource(DebugAssetApp(debug_asset_app));
|
||||
app.add_system(run_debug_asset_app);
|
||||
app.add_systems(Update, run_debug_asset_app);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@ impl<T: Asset> Default for AssetCountDiagnosticsPlugin<T> {
|
|||
|
||||
impl<T: Asset> Plugin for AssetCountDiagnosticsPlugin<T> {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_startup_system(Self::setup_system)
|
||||
.add_system(Self::diagnostic_system);
|
||||
app.add_systems(Startup, Self::setup_system)
|
||||
.add_systems(Update, Self::diagnostic_system);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,7 @@ impl AssetIo for FileAssetIo {
|
|||
to_watch: &Path,
|
||||
to_reload: Option<PathBuf>,
|
||||
) -> Result<(), AssetIoError> {
|
||||
#![allow(unused_variables)]
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
{
|
||||
let to_reload = to_reload.unwrap_or_else(|| to_watch.to_owned());
|
||||
|
|
|
@ -46,18 +46,15 @@ pub use loader::*;
|
|||
pub use path::*;
|
||||
pub use reflect::*;
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_app::{prelude::*, MainScheduleOrder};
|
||||
use bevy_ecs::schedule::ScheduleLabel;
|
||||
|
||||
/// [`SystemSet`]s for asset loading in an [`App`] schedule.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
#[system_set(base)]
|
||||
pub enum AssetSet {
|
||||
/// Asset storages are updated.
|
||||
LoadAssets,
|
||||
/// Asset events are generated.
|
||||
AssetEvents,
|
||||
}
|
||||
/// Asset storages are updated.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
|
||||
pub struct LoadAssets;
|
||||
/// Asset events are generated.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
|
||||
pub struct AssetEvents;
|
||||
|
||||
/// Adds support for [`Assets`] to an App.
|
||||
///
|
||||
|
@ -106,24 +103,19 @@ impl Plugin for AssetPlugin {
|
|||
app.insert_resource(asset_server);
|
||||
}
|
||||
|
||||
app.register_type::<HandleId>();
|
||||
|
||||
app.configure_set(
|
||||
AssetSet::LoadAssets
|
||||
.before(CoreSet::PreUpdate)
|
||||
.after(CoreSet::First),
|
||||
)
|
||||
.configure_set(
|
||||
AssetSet::AssetEvents
|
||||
.after(CoreSet::PostUpdate)
|
||||
.before(CoreSet::Last),
|
||||
)
|
||||
.add_system(asset_server::free_unused_assets_system.in_base_set(CoreSet::PreUpdate));
|
||||
app.register_type::<HandleId>()
|
||||
.add_systems(PreUpdate, asset_server::free_unused_assets_system);
|
||||
app.init_schedule(LoadAssets);
|
||||
app.init_schedule(AssetEvents);
|
||||
|
||||
#[cfg(all(
|
||||
feature = "filesystem_watcher",
|
||||
all(not(target_arch = "wasm32"), not(target_os = "android"))
|
||||
))]
|
||||
app.add_system(io::filesystem_watcher_system.in_base_set(AssetSet::LoadAssets));
|
||||
app.add_systems(LoadAssets, io::filesystem_watcher_system);
|
||||
|
||||
let mut order = app.world.resource_mut::<MainScheduleOrder>();
|
||||
order.insert_after(First, LoadAssets);
|
||||
order.insert_after(PostUpdate, AssetEvents);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
//! # use bevy_ecs::{system::Res, event::EventWriter};
|
||||
//! # use bevy_audio::{Audio, AudioPlugin};
|
||||
//! # use bevy_asset::{AssetPlugin, AssetServer};
|
||||
//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins};
|
||||
//! # use bevy_app::{App, AppExit, NoopPluginGroup as MinimalPlugins, Startup};
|
||||
//! fn main() {
|
||||
//! App::new()
|
||||
//! .add_plugins(MinimalPlugins)
|
||||
//! .add_plugin(AssetPlugin::default())
|
||||
//! .add_plugin(AudioPlugin)
|
||||
//! .add_startup_system(play_background_audio)
|
||||
//! .add_systems(Startup, play_background_audio)
|
||||
//! .run();
|
||||
//! }
|
||||
//!
|
||||
|
@ -47,7 +47,6 @@ pub use sinks::*;
|
|||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{AddAsset, Asset};
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
/// Adds support for audio playback to a Bevy Application
|
||||
///
|
||||
|
@ -62,7 +61,7 @@ impl Plugin for AudioPlugin {
|
|||
.add_asset::<AudioSink>()
|
||||
.add_asset::<SpatialAudioSink>()
|
||||
.init_resource::<Audio<AudioSource>>()
|
||||
.add_system(play_queued_audio_system::<AudioSource>.in_base_set(CoreSet::PostUpdate));
|
||||
.add_systems(PostUpdate, play_queued_audio_system::<AudioSource>);
|
||||
|
||||
#[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))]
|
||||
app.init_asset_loader::<AudioLoader>();
|
||||
|
@ -78,6 +77,6 @@ impl AddAudioSource for App {
|
|||
self.add_asset::<T>()
|
||||
.init_resource::<Audio<T>>()
|
||||
.init_resource::<AudioOutput<T>>()
|
||||
.add_system(play_queued_audio_system::<T>.in_base_set(CoreSet::PostUpdate))
|
||||
.add_systems(PostUpdate, play_queued_audio_system::<T>)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,12 +102,12 @@ pub struct TaskPoolPlugin {
|
|||
}
|
||||
|
||||
impl Plugin for TaskPoolPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
fn build(&self, _app: &mut App) {
|
||||
// Setup the default bevy task pools
|
||||
self.task_pool_options.create_default_pools();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
app.add_system(tick_global_task_pools.in_base_set(bevy_app::CoreSet::Last));
|
||||
_app.add_systems(Last, tick_global_task_pools);
|
||||
}
|
||||
}
|
||||
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
|
||||
|
@ -124,7 +124,7 @@ fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
|
|||
|
||||
/// Maintains a count of frames rendered since the start of the application.
|
||||
///
|
||||
/// [`FrameCount`] is incremented during [`CoreSet::Last`], providing predictable
|
||||
/// [`FrameCount`] is incremented during [`Last`], providing predictable
|
||||
/// behaviour: it will be 0 during the first update, 1 during the next, and so forth.
|
||||
///
|
||||
/// # Overflows
|
||||
|
@ -142,7 +142,7 @@ pub struct FrameCountPlugin;
|
|||
impl Plugin for FrameCountPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<FrameCount>();
|
||||
app.add_system(update_frame_count.in_base_set(CoreSet::Last));
|
||||
app.add_systems(Last, update_frame_count);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,13 +7,7 @@ pub use settings::{BloomCompositeMode, BloomPrefilterSettings, BloomSettings};
|
|||
use crate::{core_2d, core_3d};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, HandleUntyped};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Entity},
|
||||
query::{QueryState, With},
|
||||
schedule::IntoSystemConfig,
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
world::World,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_math::UVec2;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
|
@ -22,12 +16,12 @@ use bevy_render::{
|
|||
ComponentUniforms, DynamicUniformIndex, ExtractComponentPlugin, UniformComponentPlugin,
|
||||
},
|
||||
prelude::Color,
|
||||
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext},
|
||||
render_resource::*,
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
texture::{CachedTexture, TextureCache},
|
||||
view::ViewTarget,
|
||||
RenderApp, RenderSet,
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
#[cfg(feature = "trace")]
|
||||
use bevy_utils::tracing::info_span;
|
||||
|
@ -71,10 +65,15 @@ impl Plugin for BloomPlugin {
|
|||
.init_resource::<BloomUpsamplingPipeline>()
|
||||
.init_resource::<SpecializedRenderPipelines<BloomDownsamplingPipeline>>()
|
||||
.init_resource::<SpecializedRenderPipelines<BloomUpsamplingPipeline>>()
|
||||
.add_system(prepare_bloom_textures.in_set(RenderSet::Prepare))
|
||||
.add_system(prepare_downsampling_pipeline.in_set(RenderSet::Prepare))
|
||||
.add_system(prepare_upsampling_pipeline.in_set(RenderSet::Prepare))
|
||||
.add_system(queue_bloom_bind_groups.in_set(RenderSet::Queue));
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_bloom_textures.in_set(RenderSet::Prepare),
|
||||
prepare_downsampling_pipeline.in_set(RenderSet::Prepare),
|
||||
prepare_upsampling_pipeline.in_set(RenderSet::Prepare),
|
||||
queue_bloom_bind_groups.in_set(RenderSet::Queue),
|
||||
),
|
||||
);
|
||||
|
||||
// Add bloom to the 3d render graph
|
||||
{
|
||||
|
@ -84,12 +83,6 @@ impl Plugin for BloomPlugin {
|
|||
.get_sub_graph_mut(crate::core_3d::graph::NAME)
|
||||
.unwrap();
|
||||
draw_3d_graph.add_node(core_3d::graph::node::BLOOM, bloom_node);
|
||||
draw_3d_graph.add_slot_edge(
|
||||
draw_3d_graph.input_node().id,
|
||||
crate::core_3d::graph::input::VIEW_ENTITY,
|
||||
core_3d::graph::node::BLOOM,
|
||||
BloomNode::IN_VIEW,
|
||||
);
|
||||
// MAIN_PASS -> BLOOM -> TONEMAPPING
|
||||
draw_3d_graph.add_node_edge(
|
||||
crate::core_3d::graph::node::MAIN_PASS,
|
||||
|
@ -109,12 +102,6 @@ impl Plugin for BloomPlugin {
|
|||
.get_sub_graph_mut(crate::core_2d::graph::NAME)
|
||||
.unwrap();
|
||||
draw_2d_graph.add_node(core_2d::graph::node::BLOOM, bloom_node);
|
||||
draw_2d_graph.add_slot_edge(
|
||||
draw_2d_graph.input_node().id,
|
||||
crate::core_2d::graph::input::VIEW_ENTITY,
|
||||
core_2d::graph::node::BLOOM,
|
||||
BloomNode::IN_VIEW,
|
||||
);
|
||||
// MAIN_PASS -> BLOOM -> TONEMAPPING
|
||||
draw_2d_graph.add_node_edge(
|
||||
crate::core_2d::graph::node::MAIN_PASS,
|
||||
|
@ -142,8 +129,6 @@ pub struct BloomNode {
|
|||
}
|
||||
|
||||
impl BloomNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
view_query: QueryState::new(world),
|
||||
|
@ -152,10 +137,6 @@ impl BloomNode {
|
|||
}
|
||||
|
||||
impl Node for BloomNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.view_query.update_archetypes(world);
|
||||
}
|
||||
|
@ -175,7 +156,7 @@ impl Node for BloomNode {
|
|||
let downsampling_pipeline_res = world.resource::<BloomDownsamplingPipeline>();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let uniforms = world.resource::<ComponentUniforms<BloomUniforms>>();
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
let Ok((
|
||||
camera,
|
||||
view_target,
|
||||
|
|
|
@ -49,7 +49,7 @@ pub struct BloomSettings {
|
|||
///
|
||||
/// In this configuration:
|
||||
/// * 0.0 means no bloom
|
||||
/// * > 0.0 means a proportionate amount of scattered light is added
|
||||
/// * Greater than 0.0 means a proportionate amount of scattered light is added
|
||||
pub intensity: f32,
|
||||
|
||||
/// Low frequency contribution boost.
|
||||
|
@ -69,7 +69,7 @@ pub struct BloomSettings {
|
|||
///
|
||||
/// In this configuration:
|
||||
/// * 0.0 means no bloom
|
||||
/// * > 0.0 means a proportionate amount of scattered light is added
|
||||
/// * Greater than 0.0 means a proportionate amount of scattered light is added
|
||||
pub low_frequency_boost: f32,
|
||||
|
||||
/// Low frequency contribution boost curve.
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_phase::RenderPhase,
|
||||
render_resource::{LoadOp, Operations, RenderPassDescriptor},
|
||||
renderer::RenderContext,
|
||||
|
@ -27,8 +27,6 @@ pub struct MainPass2dNode {
|
|||
}
|
||||
|
||||
impl MainPass2dNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: world.query_filtered(),
|
||||
|
@ -37,10 +35,6 @@ impl MainPass2dNode {
|
|||
}
|
||||
|
||||
impl Node for MainPass2dNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(MainPass2dNode::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
@ -51,7 +45,7 @@ impl Node for MainPass2dNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
let (camera, transparent_phase, target, camera_2d) =
|
||||
if let Ok(result) = self.query.get_manual(world, view_entity) {
|
||||
result
|
||||
|
|
|
@ -20,18 +20,18 @@ pub mod graph {
|
|||
pub use camera_2d::*;
|
||||
pub use main_pass_2d_node::*;
|
||||
|
||||
use bevy_app::{App, IntoSystemAppConfig, Plugin};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::Camera,
|
||||
extract_component::ExtractComponentPlugin,
|
||||
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
||||
render_graph::{EmptyNode, RenderGraph},
|
||||
render_phase::{
|
||||
batch_phase_system, sort_phase_system, BatchedPhaseItem, CachedRenderPipelinePhaseItem,
|
||||
DrawFunctionId, DrawFunctions, PhaseItem, RenderPhase,
|
||||
},
|
||||
render_resource::CachedRenderPipelineId,
|
||||
Extract, ExtractSchedule, RenderApp, RenderSet,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::FloatOrd;
|
||||
use std::ops::Range;
|
||||
|
@ -52,12 +52,15 @@ impl Plugin for Core2dPlugin {
|
|||
|
||||
render_app
|
||||
.init_resource::<DrawFunctions<Transparent2d>>()
|
||||
.add_system(extract_core_2d_camera_phases.in_schedule(ExtractSchedule))
|
||||
.add_system(sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort))
|
||||
.add_system(
|
||||
batch_phase_system::<Transparent2d>
|
||||
.after(sort_phase_system::<Transparent2d>)
|
||||
.in_set(RenderSet::PhaseSort),
|
||||
.add_systems(ExtractSchedule, extract_core_2d_camera_phases)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
sort_phase_system::<Transparent2d>.in_set(RenderSet::PhaseSort),
|
||||
batch_phase_system::<Transparent2d>
|
||||
.after(sort_phase_system::<Transparent2d>)
|
||||
.in_set(RenderSet::PhaseSort),
|
||||
),
|
||||
);
|
||||
|
||||
let pass_node_2d = MainPass2dNode::new(&mut render_app.world);
|
||||
|
@ -70,28 +73,6 @@ impl Plugin for Core2dPlugin {
|
|||
draw_2d_graph.add_node(graph::node::TONEMAPPING, tonemapping);
|
||||
draw_2d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode);
|
||||
draw_2d_graph.add_node(graph::node::UPSCALING, upscaling);
|
||||
let input_node_id = draw_2d_graph.set_input(vec![SlotInfo::new(
|
||||
graph::input::VIEW_ENTITY,
|
||||
SlotType::Entity,
|
||||
)]);
|
||||
draw_2d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::MAIN_PASS,
|
||||
MainPass2dNode::IN_VIEW,
|
||||
);
|
||||
draw_2d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::TONEMAPPING,
|
||||
TonemappingNode::IN_VIEW,
|
||||
);
|
||||
draw_2d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::UPSCALING,
|
||||
UpscalingNode::IN_VIEW,
|
||||
);
|
||||
draw_2d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING);
|
||||
draw_2d_graph.add_node_edge(
|
||||
graph::node::TONEMAPPING,
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_phase::RenderPhase,
|
||||
render_resource::{
|
||||
Extent3d, LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
||||
|
@ -38,8 +38,6 @@ pub struct MainPass3dNode {
|
|||
}
|
||||
|
||||
impl MainPass3dNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: world.query_filtered(),
|
||||
|
@ -48,10 +46,6 @@ impl MainPass3dNode {
|
|||
}
|
||||
|
||||
impl Node for MainPass3dNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(MainPass3dNode::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
@ -62,7 +56,7 @@ impl Node for MainPass3dNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
let Ok((
|
||||
camera,
|
||||
opaque_phase,
|
||||
|
|
|
@ -23,13 +23,13 @@ use std::cmp::Reverse;
|
|||
pub use camera_3d::*;
|
||||
pub use main_pass_3d_node::*;
|
||||
|
||||
use bevy_app::{App, IntoSystemAppConfig, Plugin};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::{Camera, ExtractedCamera},
|
||||
extract_component::ExtractComponentPlugin,
|
||||
prelude::Msaa,
|
||||
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
||||
render_graph::{EmptyNode, RenderGraph},
|
||||
render_phase::{
|
||||
sort_phase_system, CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, PhaseItem,
|
||||
RenderPhase,
|
||||
|
@ -41,7 +41,7 @@ use bevy_render::{
|
|||
renderer::RenderDevice,
|
||||
texture::{BevyDefault, TextureCache},
|
||||
view::{ExtractedView, ViewDepthTexture, ViewTarget},
|
||||
Extract, ExtractSchedule, RenderApp, RenderSet,
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bevy_utils::{FloatOrd, HashMap};
|
||||
|
||||
|
@ -68,20 +68,21 @@ impl Plugin for Core3dPlugin {
|
|||
.init_resource::<DrawFunctions<Opaque3d>>()
|
||||
.init_resource::<DrawFunctions<AlphaMask3d>>()
|
||||
.init_resource::<DrawFunctions<Transparent3d>>()
|
||||
.add_system(extract_core_3d_camera_phases.in_schedule(ExtractSchedule))
|
||||
.add_system(
|
||||
prepare_core_3d_depth_textures
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(bevy_render::view::prepare_windows),
|
||||
)
|
||||
.add_system(
|
||||
prepare_core_3d_transmission_textures
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(bevy_render::view::prepare_windows),
|
||||
)
|
||||
.add_system(sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort))
|
||||
.add_system(sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort))
|
||||
.add_system(sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort));
|
||||
.add_systems(ExtractSchedule, extract_core_3d_camera_phases)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_core_3d_depth_textures
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(bevy_render::view::prepare_windows),
|
||||
prepare_core_3d_transmission_textures
|
||||
.in_set(RenderSet::Prepare)
|
||||
.after(bevy_render::view::prepare_windows),
|
||||
sort_phase_system::<Opaque3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<AlphaMask3d>.in_set(RenderSet::PhaseSort),
|
||||
sort_phase_system::<Transparent3d>.in_set(RenderSet::PhaseSort),
|
||||
),
|
||||
);
|
||||
|
||||
let prepass_node = PrepassNode::new(&mut render_app.world);
|
||||
let pass_node_3d = MainPass3dNode::new(&mut render_app.world);
|
||||
|
@ -96,34 +97,6 @@ impl Plugin for Core3dPlugin {
|
|||
draw_3d_graph.add_node(graph::node::END_MAIN_PASS_POST_PROCESSING, EmptyNode);
|
||||
draw_3d_graph.add_node(graph::node::UPSCALING, upscaling);
|
||||
|
||||
let input_node_id = draw_3d_graph.set_input(vec![SlotInfo::new(
|
||||
graph::input::VIEW_ENTITY,
|
||||
SlotType::Entity,
|
||||
)]);
|
||||
draw_3d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::PREPASS,
|
||||
PrepassNode::IN_VIEW,
|
||||
);
|
||||
draw_3d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::MAIN_PASS,
|
||||
MainPass3dNode::IN_VIEW,
|
||||
);
|
||||
draw_3d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::TONEMAPPING,
|
||||
TonemappingNode::IN_VIEW,
|
||||
);
|
||||
draw_3d_graph.add_slot_edge(
|
||||
input_node_id,
|
||||
graph::input::VIEW_ENTITY,
|
||||
graph::node::UPSCALING,
|
||||
UpscalingNode::IN_VIEW,
|
||||
);
|
||||
draw_3d_graph.add_node_edge(graph::node::PREPASS, graph::node::MAIN_PASS);
|
||||
draw_3d_graph.add_node_edge(graph::node::MAIN_PASS, graph::node::TONEMAPPING);
|
||||
draw_3d_graph.add_node_edge(
|
||||
|
|
|
@ -14,7 +14,7 @@ use bevy_render::{
|
|||
renderer::RenderDevice,
|
||||
texture::BevyDefault,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
RenderApp, RenderSet,
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
mod node;
|
||||
|
@ -90,7 +90,7 @@ impl Plugin for FxaaPlugin {
|
|||
render_app
|
||||
.init_resource::<FxaaPipeline>()
|
||||
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
|
||||
.add_system(prepare_fxaa_pipelines.in_set(RenderSet::Prepare));
|
||||
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare));
|
||||
|
||||
{
|
||||
let fxaa_node = FxaaNode::new(&mut render_app.world);
|
||||
|
@ -99,13 +99,6 @@ impl Plugin for FxaaPlugin {
|
|||
|
||||
graph.add_node(core_3d::graph::node::FXAA, fxaa_node);
|
||||
|
||||
graph.add_slot_edge(
|
||||
graph.input_node().id,
|
||||
core_3d::graph::input::VIEW_ENTITY,
|
||||
core_3d::graph::node::FXAA,
|
||||
FxaaNode::IN_VIEW,
|
||||
);
|
||||
|
||||
graph.add_node_edge(
|
||||
core_3d::graph::node::TONEMAPPING,
|
||||
core_3d::graph::node::FXAA,
|
||||
|
@ -122,13 +115,6 @@ impl Plugin for FxaaPlugin {
|
|||
|
||||
graph.add_node(core_2d::graph::node::FXAA, fxaa_node);
|
||||
|
||||
graph.add_slot_edge(
|
||||
graph.input_node().id,
|
||||
core_2d::graph::input::VIEW_ENTITY,
|
||||
core_2d::graph::node::FXAA,
|
||||
FxaaNode::IN_VIEW,
|
||||
);
|
||||
|
||||
graph.add_node_edge(
|
||||
core_2d::graph::node::TONEMAPPING,
|
||||
core_2d::graph::node::FXAA,
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::fxaa::{CameraFxaaPipeline, Fxaa, FxaaPipeline};
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::query::QueryState;
|
||||
use bevy_render::{
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, FilterMode, Operations,
|
||||
PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor,
|
||||
|
@ -28,8 +28,6 @@ pub struct FxaaNode {
|
|||
}
|
||||
|
||||
impl FxaaNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: QueryState::new(world),
|
||||
|
@ -39,10 +37,6 @@ impl FxaaNode {
|
|||
}
|
||||
|
||||
impl Node for FxaaNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(FxaaNode::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
@ -53,7 +47,7 @@ impl Node for FxaaNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let fxaa_pipeline = world.resource::<FxaaPipeline>();
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ use bevy_app::{App, Plugin};
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext},
|
||||
renderer::RenderContext,
|
||||
view::{Msaa, ViewTarget},
|
||||
RenderSet,
|
||||
Render, RenderSet,
|
||||
};
|
||||
use bevy_render::{render_resource::*, RenderApp};
|
||||
|
||||
|
@ -20,12 +20,14 @@ impl Plugin for MsaaWritebackPlugin {
|
|||
return
|
||||
};
|
||||
|
||||
render_app.add_system(queue_msaa_writeback_pipelines.in_set(RenderSet::Queue));
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
queue_msaa_writeback_pipelines.in_set(RenderSet::Queue),
|
||||
);
|
||||
let msaa_writeback_2d = MsaaWritebackNode::new(&mut render_app.world);
|
||||
let msaa_writeback_3d = MsaaWritebackNode::new(&mut render_app.world);
|
||||
let mut graph = render_app.world.resource_mut::<RenderGraph>();
|
||||
if let Some(core_2d) = graph.get_sub_graph_mut(crate::core_2d::graph::NAME) {
|
||||
let input_node = core_2d.input_node().id;
|
||||
core_2d.add_node(
|
||||
crate::core_2d::graph::node::MSAA_WRITEBACK,
|
||||
msaa_writeback_2d,
|
||||
|
@ -34,16 +36,9 @@ impl Plugin for MsaaWritebackPlugin {
|
|||
crate::core_2d::graph::node::MSAA_WRITEBACK,
|
||||
crate::core_2d::graph::node::MAIN_PASS,
|
||||
);
|
||||
core_2d.add_slot_edge(
|
||||
input_node,
|
||||
crate::core_2d::graph::input::VIEW_ENTITY,
|
||||
crate::core_2d::graph::node::MSAA_WRITEBACK,
|
||||
MsaaWritebackNode::IN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(core_3d) = graph.get_sub_graph_mut(crate::core_3d::graph::NAME) {
|
||||
let input_node = core_3d.input_node().id;
|
||||
core_3d.add_node(
|
||||
crate::core_3d::graph::node::MSAA_WRITEBACK,
|
||||
msaa_writeback_3d,
|
||||
|
@ -52,12 +47,6 @@ impl Plugin for MsaaWritebackPlugin {
|
|||
crate::core_3d::graph::node::MSAA_WRITEBACK,
|
||||
crate::core_3d::graph::node::MAIN_PASS,
|
||||
);
|
||||
core_3d.add_slot_edge(
|
||||
input_node,
|
||||
crate::core_3d::graph::input::VIEW_ENTITY,
|
||||
crate::core_3d::graph::node::MSAA_WRITEBACK,
|
||||
MsaaWritebackNode::IN_VIEW,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,8 +56,6 @@ pub struct MsaaWritebackNode {
|
|||
}
|
||||
|
||||
impl MsaaWritebackNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
cameras: world.query(),
|
||||
|
@ -77,9 +64,6 @@ impl MsaaWritebackNode {
|
|||
}
|
||||
|
||||
impl Node for MsaaWritebackNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.cameras.update_archetypes(world);
|
||||
}
|
||||
|
@ -89,7 +73,7 @@ impl Node for MsaaWritebackNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
if let Ok((target, blit_pipeline_id)) = self.cameras.get_manual(world, view_entity) {
|
||||
let blit_pipeline = world.resource::<BlitPipeline>();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
|
|
|
@ -3,7 +3,7 @@ use bevy_ecs::query::QueryState;
|
|||
use bevy_render::{
|
||||
camera::ExtractedCamera,
|
||||
prelude::Color,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_phase::RenderPhase,
|
||||
render_resource::{
|
||||
LoadOp, Operations, RenderPassColorAttachment, RenderPassDepthStencilAttachment,
|
||||
|
@ -34,8 +34,6 @@ pub struct PrepassNode {
|
|||
}
|
||||
|
||||
impl PrepassNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
main_view_query: QueryState::new(world),
|
||||
|
@ -44,10 +42,6 @@ impl PrepassNode {
|
|||
}
|
||||
|
||||
impl Node for PrepassNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.main_view_query.update_archetypes(world);
|
||||
}
|
||||
|
@ -58,7 +52,7 @@ impl Node for PrepassNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
let Ok((
|
||||
camera,
|
||||
opaque_prepass_phase,
|
||||
|
|
|
@ -10,7 +10,7 @@ use bevy_render::render_asset::RenderAssets;
|
|||
use bevy_render::renderer::RenderDevice;
|
||||
use bevy_render::texture::{CompressedImageFormats, Image, ImageSampler, ImageType};
|
||||
use bevy_render::view::{ViewTarget, ViewUniform};
|
||||
use bevy_render::{render_resource::*, RenderApp, RenderSet};
|
||||
use bevy_render::{render_resource::*, Render, RenderApp, RenderSet};
|
||||
|
||||
mod node;
|
||||
|
||||
|
@ -94,7 +94,10 @@ impl Plugin for TonemappingPlugin {
|
|||
render_app
|
||||
.init_resource::<TonemappingPipeline>()
|
||||
.init_resource::<SpecializedRenderPipelines<TonemappingPipeline>>()
|
||||
.add_system(queue_view_tonemapping_pipelines.in_set(RenderSet::Queue));
|
||||
.add_systems(
|
||||
Render,
|
||||
queue_view_tonemapping_pipelines.in_set(RenderSet::Queue),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use bevy_ecs::prelude::*;
|
|||
use bevy_ecs::query::QueryState;
|
||||
use bevy_render::{
|
||||
render_asset::RenderAssets,
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, BufferId, LoadOp,
|
||||
Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
|
||||
|
@ -34,8 +34,6 @@ pub struct TonemappingNode {
|
|||
}
|
||||
|
||||
impl TonemappingNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: QueryState::new(world),
|
||||
|
@ -46,10 +44,6 @@ impl TonemappingNode {
|
|||
}
|
||||
|
||||
impl Node for TonemappingNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(TonemappingNode::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
@ -60,7 +54,7 @@ impl Node for TonemappingNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let tonemapping_pipeline = world.resource::<TonemappingPipeline>();
|
||||
let gpu_images = world.get_resource::<RenderAssets<Image>>().unwrap();
|
||||
|
|
|
@ -3,7 +3,7 @@ use bevy_app::prelude::*;
|
|||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::camera::{CameraOutputMode, ExtractedCamera};
|
||||
use bevy_render::view::ViewTarget;
|
||||
use bevy_render::{render_resource::*, RenderApp, RenderSet};
|
||||
use bevy_render::{render_resource::*, Render, RenderApp, RenderSet};
|
||||
|
||||
mod node;
|
||||
|
||||
|
@ -14,7 +14,10 @@ pub struct UpscalingPlugin;
|
|||
impl Plugin for UpscalingPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.add_system(queue_view_upscaling_pipelines.in_set(RenderSet::Queue));
|
||||
render_app.add_systems(
|
||||
Render,
|
||||
queue_view_upscaling_pipelines.in_set(RenderSet::Queue),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ use bevy_ecs::prelude::*;
|
|||
use bevy_ecs::query::QueryState;
|
||||
use bevy_render::{
|
||||
camera::{CameraOutputMode, ExtractedCamera},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
render_resource::{
|
||||
BindGroup, BindGroupDescriptor, BindGroupEntry, BindingResource, LoadOp, Operations,
|
||||
PipelineCache, RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor,
|
||||
|
@ -27,8 +27,6 @@ pub struct UpscalingNode {
|
|||
}
|
||||
|
||||
impl UpscalingNode {
|
||||
pub const IN_VIEW: &'static str = "view";
|
||||
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
Self {
|
||||
query: QueryState::new(world),
|
||||
|
@ -38,10 +36,6 @@ impl UpscalingNode {
|
|||
}
|
||||
|
||||
impl Node for UpscalingNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
vec![SlotInfo::new(UpscalingNode::IN_VIEW, SlotType::Entity)]
|
||||
}
|
||||
|
||||
fn update(&mut self, world: &mut World) {
|
||||
self.query.update_archetypes(world);
|
||||
}
|
||||
|
@ -52,7 +46,7 @@ impl Node for UpscalingNode {
|
|||
render_context: &mut RenderContext,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
|
||||
let view_entity = graph.view_entity();
|
||||
|
||||
let pipeline_cache = world.get_resource::<PipelineCache>().unwrap();
|
||||
let blit_pipeline = world.get_resource::<BlitPipeline>().unwrap();
|
||||
|
|
|
@ -9,8 +9,8 @@ pub struct EntityCountDiagnosticsPlugin;
|
|||
|
||||
impl Plugin for EntityCountDiagnosticsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_startup_system(Self::setup_system)
|
||||
.add_system(Self::diagnostic_system);
|
||||
app.add_systems(Startup, Self::setup_system)
|
||||
.add_systems(Update, Self::diagnostic_system);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,8 +10,8 @@ pub struct FrameTimeDiagnosticsPlugin;
|
|||
|
||||
impl Plugin for FrameTimeDiagnosticsPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_startup_system(Self::setup_system)
|
||||
.add_system(Self::diagnostic_system);
|
||||
app.add_systems(Startup, Self::setup_system)
|
||||
.add_systems(Update, Self::diagnostic_system);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ pub struct DiagnosticsPlugin;
|
|||
|
||||
impl Plugin for DiagnosticsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<Diagnostics>()
|
||||
.add_startup_system(system_information_diagnostics_plugin::internal::log_system_info);
|
||||
app.init_resource::<Diagnostics>().add_systems(
|
||||
Startup,
|
||||
system_information_diagnostics_plugin::internal::log_system_info,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,9 +37,9 @@ impl Plugin for LogDiagnosticsPlugin {
|
|||
});
|
||||
|
||||
if self.debug {
|
||||
app.add_system(Self::log_diagnostics_debug_system.in_base_set(CoreSet::PostUpdate));
|
||||
app.add_systems(PostUpdate, Self::log_diagnostics_debug_system);
|
||||
} else {
|
||||
app.add_system(Self::log_diagnostics_system.in_base_set(CoreSet::PostUpdate));
|
||||
app.add_systems(PostUpdate, Self::log_diagnostics_system);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ use bevy_app::prelude::*;
|
|||
pub struct SystemInformationDiagnosticsPlugin;
|
||||
impl Plugin for SystemInformationDiagnosticsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_startup_system(internal::setup_system)
|
||||
.add_system(internal::diagnostic_system);
|
||||
app.add_systems(Startup, internal::setup_system)
|
||||
.add_systems(Update, internal::diagnostic_system);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ fixedbitset = "0.4.2"
|
|||
rustc-hash = "1.1"
|
||||
downcast-rs = "1.2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
thiserror = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
|
|
|
@ -150,7 +150,7 @@ fn main() {
|
|||
let mut schedule = Schedule::default();
|
||||
|
||||
// Add our system to the schedule
|
||||
schedule.add_system(movement);
|
||||
schedule.add_systems(movement);
|
||||
|
||||
// Run the schedule once. If your app has a "loop", you would run this once per loop
|
||||
schedule.run(&mut world);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use bevy_ecs::{prelude::*, schedule::IntoSystemConfig};
|
||||
use bevy_ecs::prelude::*;
|
||||
use rand::Rng;
|
||||
use std::ops::Deref;
|
||||
|
||||
|
@ -21,11 +21,13 @@ fn main() {
|
|||
|
||||
// Add systems to the Schedule to execute our app logic
|
||||
// We can label our systems to force a specific run-order between some of them
|
||||
schedule.add_system(spawn_entities.in_set(SimulationSystem::Spawn));
|
||||
schedule.add_system(print_counter_when_changed.after(SimulationSystem::Spawn));
|
||||
schedule.add_system(age_all_entities.in_set(SimulationSystem::Age));
|
||||
schedule.add_system(remove_old_entities.after(SimulationSystem::Age));
|
||||
schedule.add_system(print_changed_entities.after(SimulationSystem::Age));
|
||||
schedule.add_systems((
|
||||
spawn_entities.in_set(SimulationSet::Spawn),
|
||||
print_counter_when_changed.after(SimulationSet::Spawn),
|
||||
age_all_entities.in_set(SimulationSet::Age),
|
||||
remove_old_entities.after(SimulationSet::Age),
|
||||
print_changed_entities.after(SimulationSet::Age),
|
||||
));
|
||||
|
||||
// Simulate 10 frames in our world
|
||||
for iteration in 1..=10 {
|
||||
|
@ -48,7 +50,7 @@ struct Age {
|
|||
|
||||
// System sets can be used to group systems and configured to control relative ordering
|
||||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum SimulationSystem {
|
||||
enum SimulationSet {
|
||||
Spawn,
|
||||
Age,
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@ fn main() {
|
|||
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct FlushEvents;
|
||||
|
||||
schedule.add_system(Events::<MyEvent>::update_system.in_set(FlushEvents));
|
||||
schedule.add_systems(Events::<MyEvent>::update_system.in_set(FlushEvents));
|
||||
|
||||
// Add systems sending and receiving events after the events are flushed.
|
||||
schedule.add_system(sending_system.after(FlushEvents));
|
||||
schedule.add_system(receiving_system.after(sending_system));
|
||||
schedule.add_systems((
|
||||
sending_system.after(FlushEvents),
|
||||
receiving_system.after(sending_system),
|
||||
));
|
||||
|
||||
// Simulate 10 frames of our world
|
||||
for iteration in 1..=10 {
|
||||
|
|
|
@ -15,8 +15,7 @@ fn main() {
|
|||
let mut schedule = Schedule::default();
|
||||
|
||||
// Add systems to increase the counter and to print out the current value
|
||||
schedule.add_system(increase_counter);
|
||||
schedule.add_system(print_counter.after(increase_counter));
|
||||
schedule.add_systems((increase_counter, print_counter).chain());
|
||||
|
||||
for iteration in 1..=10 {
|
||||
println!("Simulating frame {iteration}/10");
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use bevy_macro_utils::ensure_no_collision;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
parse_quote,
|
||||
parse_macro_input, parse_quote,
|
||||
punctuated::Punctuated,
|
||||
Attribute, Data, DataStruct, DeriveInput, Field, Fields,
|
||||
};
|
||||
|
@ -25,7 +26,10 @@ mod field_attr_keywords {
|
|||
|
||||
pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query";
|
||||
|
||||
pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
||||
pub fn derive_world_query_impl(input: TokenStream) -> TokenStream {
|
||||
let tokens = input.clone();
|
||||
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let visibility = ast.vis;
|
||||
|
||||
let mut fetch_struct_attributes = FetchStructAttributes::default();
|
||||
|
@ -104,13 +108,18 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
};
|
||||
|
||||
let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site());
|
||||
let fetch_struct_name = ensure_no_collision(fetch_struct_name, tokens.clone());
|
||||
let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable {
|
||||
Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site())
|
||||
let new_ident = Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site());
|
||||
ensure_no_collision(new_ident, tokens.clone())
|
||||
} else {
|
||||
fetch_struct_name.clone()
|
||||
};
|
||||
|
||||
// Generate a name for the state struct that doesn't conflict
|
||||
// with the struct definition.
|
||||
let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site());
|
||||
let state_struct_name = ensure_no_collision(state_struct_name, tokens);
|
||||
|
||||
let fields = match &ast.data {
|
||||
Data::Struct(DataStruct {
|
||||
|
@ -176,7 +185,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
&field_types
|
||||
};
|
||||
|
||||
quote! {
|
||||
let item_struct = quote! {
|
||||
#derive_macro_call
|
||||
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
|
@ -186,7 +195,9 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)*
|
||||
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
};
|
||||
|
||||
let query_impl = quote! {
|
||||
#[doc(hidden)]
|
||||
#[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
|
@ -222,16 +233,16 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
unsafe fn init_fetch<'__w>(
|
||||
_world: &'__w #path::world::World,
|
||||
state: &Self::State,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32
|
||||
_last_run: #path::component::Tick,
|
||||
_this_run: #path::component::Tick,
|
||||
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
|
||||
#fetch_struct_name {
|
||||
#(#field_idents:
|
||||
<#field_types>::init_fetch(
|
||||
_world,
|
||||
&state.#field_idents,
|
||||
_last_change_tick,
|
||||
_change_tick
|
||||
_last_run,
|
||||
_this_run,
|
||||
),
|
||||
)*
|
||||
#(#ignored_field_idents: Default::default(),)*
|
||||
|
@ -324,26 +335,28 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(item_struct, query_impl)
|
||||
};
|
||||
|
||||
let mutable_impl = impl_fetch(false);
|
||||
let readonly_impl = if fetch_struct_attributes.is_mutable {
|
||||
let world_query_impl = impl_fetch(true);
|
||||
quote! {
|
||||
let (mutable_struct, mutable_impl) = impl_fetch(false);
|
||||
let (read_only_struct, read_only_impl) = if fetch_struct_attributes.is_mutable {
|
||||
let (readonly_state, read_only_impl) = impl_fetch(true);
|
||||
let read_only_structs = quote! {
|
||||
#[doc = "Automatically generated [`WorldQuery`] type for a read-only variant of [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`]."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {
|
||||
#( #field_idents: #read_only_field_types, )*
|
||||
#( #field_visibilities #field_idents: #read_only_field_types, )*
|
||||
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
|
||||
#world_query_impl
|
||||
}
|
||||
#readonly_state
|
||||
};
|
||||
(read_only_structs, read_only_impl)
|
||||
} else {
|
||||
quote! {}
|
||||
(quote! {}, quote! {})
|
||||
};
|
||||
|
||||
let read_only_asserts = if fetch_struct_attributes.is_mutable {
|
||||
|
@ -367,24 +380,30 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
};
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#mutable_impl
|
||||
#mutable_struct
|
||||
|
||||
#readonly_impl
|
||||
|
||||
#[doc(hidden)]
|
||||
#[doc = "Automatically generated internal [`WorldQuery`] state type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`], used for caching."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
|
||||
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
|
||||
#(#ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
#read_only_struct
|
||||
|
||||
/// SAFETY: we assert fields are readonly below
|
||||
unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery
|
||||
for #read_only_struct_name #user_ty_generics #user_where_clauses {}
|
||||
|
||||
const _: () = {
|
||||
#[doc(hidden)]
|
||||
#[doc = "Automatically generated internal [`WorldQuery`] state type for [`"]
|
||||
#[doc = stringify!(#struct_name)]
|
||||
#[doc = "`], used for caching."]
|
||||
#[automatically_derived]
|
||||
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
|
||||
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
|
||||
#(#ignored_field_idents: #ignored_field_types,)*
|
||||
}
|
||||
|
||||
#mutable_impl
|
||||
|
||||
#read_only_impl
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
const _: () = {
|
||||
fn assert_readonly<T>()
|
||||
|
@ -412,7 +431,6 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
|
|||
#(q.#ignored_field_idents;)*
|
||||
#(q2.#field_idents;)*
|
||||
#(q2.#ignored_field_idents;)*
|
||||
|
||||
}
|
||||
};
|
||||
})
|
||||
|
|
|
@ -6,7 +6,9 @@ mod set;
|
|||
mod states;
|
||||
|
||||
use crate::{fetch::derive_world_query_impl, set::derive_set};
|
||||
use bevy_macro_utils::{derive_boxed_label, get_named_struct_fields, BevyManifest};
|
||||
use bevy_macro_utils::{
|
||||
derive_boxed_label, ensure_no_collision, get_named_struct_fields, BevyManifest,
|
||||
};
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::{format_ident, quote};
|
||||
|
@ -126,7 +128,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||
#(#field_from_components)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
||||
#[allow(unused_variables)]
|
||||
#[inline]
|
||||
fn get_components(
|
||||
|
@ -227,7 +231,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream {
|
|||
state: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
ParamSet {
|
||||
param_states: state,
|
||||
|
@ -258,6 +262,7 @@ static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
|
|||
/// Implement `SystemParam` to use a struct as a parameter in a system
|
||||
#[proc_macro_derive(SystemParam, attributes(system_param))]
|
||||
pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
||||
let token_stream = input.clone();
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let syn::Data::Struct(syn::DataStruct { fields: field_definitions, ..}) = ast.data else {
|
||||
return syn::Error::new(ast.span(), "Invalid `SystemParam` type: expected a `struct`")
|
||||
|
@ -392,6 +397,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
|
||||
let struct_name = &ast.ident;
|
||||
let state_struct_visibility = &ast.vis;
|
||||
let state_struct_name = ensure_no_collision(format_ident!("FetchState"), token_stream);
|
||||
|
||||
TokenStream::from(quote! {
|
||||
// We define the FetchState struct in an anonymous scope to avoid polluting the user namespace.
|
||||
|
@ -399,7 +405,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
// <EventReader<'static, 'static, T> as SystemParam>::State
|
||||
const _: () = {
|
||||
#[doc(hidden)]
|
||||
#state_struct_visibility struct FetchState <'w, 's, #(#lifetimeless_generics,)*>
|
||||
#state_struct_visibility struct #state_struct_name <'w, 's, #(#lifetimeless_generics,)*>
|
||||
#where_clause {
|
||||
state: (#(<#tuple_types as #path::system::SystemParam>::State,)*),
|
||||
marker: std::marker::PhantomData<(
|
||||
|
@ -409,11 +415,11 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
unsafe impl<'w, 's, #punctuated_generics> #path::system::SystemParam for #struct_name #ty_generics #where_clause {
|
||||
type State = FetchState<'static, 'static, #punctuated_generic_idents>;
|
||||
type State = #state_struct_name<'static, 'static, #punctuated_generic_idents>;
|
||||
type Item<'_w, '_s> = #struct_name <#(#shadowed_lifetimes,)* #punctuated_generic_idents>;
|
||||
|
||||
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
|
||||
FetchState {
|
||||
#state_struct_name {
|
||||
state: <(#(#tuple_types,)*) as #path::system::SystemParam>::init_state(world, system_meta),
|
||||
marker: std::marker::PhantomData,
|
||||
}
|
||||
|
@ -431,7 +437,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
state: &'s2 mut Self::State,
|
||||
system_meta: &#path::system::SystemMeta,
|
||||
world: &'w2 #path::world::World,
|
||||
change_tick: u32,
|
||||
change_tick: #path::component::Tick,
|
||||
) -> Self::Item<'w2, 's2> {
|
||||
let (#(#tuple_patterns,)*) = <
|
||||
(#(#tuple_types,)*) as #path::system::SystemParam
|
||||
|
@ -452,8 +458,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
|
|||
/// Implement `WorldQuery` to use a struct as a parameter in a query
|
||||
#[proc_macro_derive(WorldQuery, attributes(world_query))]
|
||||
pub fn derive_world_query(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
derive_world_query_impl(ast)
|
||||
derive_world_query_impl(input)
|
||||
}
|
||||
|
||||
/// Derive macro generating an impl of the trait `ScheduleLabel`.
|
||||
|
@ -469,7 +474,7 @@ pub fn derive_schedule_label(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
/// Derive macro generating an impl of the trait `SystemSet`.
|
||||
#[proc_macro_derive(SystemSet, attributes(system_set))]
|
||||
#[proc_macro_derive(SystemSet)]
|
||||
pub fn derive_system_set(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as DeriveInput);
|
||||
let mut trait_path = bevy_ecs_path();
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
use proc_macro::TokenStream;
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
|
||||
pub static SYSTEM_SET_ATTRIBUTE_NAME: &str = "system_set";
|
||||
pub static BASE_ATTRIBUTE_NAME: &str = "base";
|
||||
use quote::quote;
|
||||
|
||||
/// Derive a set trait
|
||||
///
|
||||
|
@ -12,55 +8,8 @@ pub static BASE_ATTRIBUTE_NAME: &str = "base";
|
|||
/// - `input`: The [`syn::DeriveInput`] for the struct that we want to derive the set trait for
|
||||
/// - `trait_path`: The [`syn::Path`] to the set trait
|
||||
pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStream {
|
||||
let mut base_trait_path = trait_path.clone();
|
||||
let ident = &mut base_trait_path.segments.last_mut().unwrap().ident;
|
||||
*ident = format_ident!("Base{ident}");
|
||||
|
||||
let mut free_trait_path = trait_path.clone();
|
||||
let ident = &mut free_trait_path.segments.last_mut().unwrap().ident;
|
||||
*ident = format_ident!("Free{ident}");
|
||||
|
||||
let ident = input.ident;
|
||||
|
||||
let mut is_base = false;
|
||||
for attr in &input.attrs {
|
||||
if !attr
|
||||
.path
|
||||
.get_ident()
|
||||
.map_or(false, |ident| ident == SYSTEM_SET_ATTRIBUTE_NAME)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
attr.parse_args_with(|input: ParseStream| {
|
||||
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
|
||||
for meta in meta {
|
||||
let ident = meta.path().get_ident().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Unrecognized attribute: `{}`",
|
||||
meta.path().to_token_stream()
|
||||
)
|
||||
});
|
||||
if ident == BASE_ATTRIBUTE_NAME {
|
||||
if let syn::Meta::Path(_) = meta {
|
||||
is_base = true;
|
||||
} else {
|
||||
panic!(
|
||||
"The `{BASE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
panic!(
|
||||
"Unrecognized attribute: `{}`",
|
||||
meta.path().to_token_stream()
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap_or_else(|_| panic!("Invalid `{SYSTEM_SET_ATTRIBUTE_NAME}` attribute format"));
|
||||
}
|
||||
|
||||
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
|
||||
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
|
||||
where_token: Default::default(),
|
||||
|
@ -73,28 +22,12 @@ pub fn derive_set(input: syn::DeriveInput, trait_path: &syn::Path) -> TokenStrea
|
|||
.unwrap(),
|
||||
);
|
||||
|
||||
let marker_impl = if is_base {
|
||||
quote! {
|
||||
impl #impl_generics #base_trait_path for #ident #ty_generics #where_clause {}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
impl #impl_generics #free_trait_path for #ident #ty_generics #where_clause {}
|
||||
}
|
||||
};
|
||||
|
||||
(quote! {
|
||||
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
|
||||
fn is_base(&self) -> bool {
|
||||
#is_base
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> std::boxed::Box<dyn #trait_path> {
|
||||
std::boxed::Box::new(std::clone::Clone::clone(self))
|
||||
}
|
||||
}
|
||||
|
||||
#marker_impl
|
||||
})
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -648,6 +648,9 @@ impl Archetypes {
|
|||
self.archetypes.get(id.index())
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `a` and `b` are equal.
|
||||
#[inline]
|
||||
pub(crate) fn get_2_mut(
|
||||
&mut self,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
//! This module contains the [`Bundle`] trait and some other helper types.
|
||||
|
||||
pub use bevy_ecs_macros::Bundle;
|
||||
use bevy_utils::HashSet;
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
|
||||
use crate::{
|
||||
archetype::{
|
||||
|
@ -12,6 +12,7 @@ use crate::{
|
|||
},
|
||||
component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
query::DebugCheckedUnwrap,
|
||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||
TypeIdMap,
|
||||
};
|
||||
|
@ -135,10 +136,10 @@ use std::any::TypeId;
|
|||
/// [`Query`]: crate::system::Query
|
||||
// Some safety points:
|
||||
// - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the
|
||||
// bundle, in the _exact_ order that [`Bundle::get_components`] is called.
|
||||
// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called.
|
||||
// - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by
|
||||
// [`Bundle::component_ids`].
|
||||
pub unsafe trait Bundle: Send + Sync + 'static {
|
||||
pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static {
|
||||
/// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s
|
||||
#[doc(hidden)]
|
||||
fn component_ids(
|
||||
|
@ -159,7 +160,10 @@ pub unsafe trait Bundle: Send + Sync + 'static {
|
|||
// Ensure that the `OwningPtr` is used correctly
|
||||
F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>,
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// The parts from [`Bundle`] that don't require statically knowing the components of the bundle.
|
||||
pub trait DynamicBundle {
|
||||
// SAFETY:
|
||||
// The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the
|
||||
// component being fetched.
|
||||
|
@ -192,7 +196,9 @@ unsafe impl<C: Component> Bundle for C {
|
|||
// Safety: The id given in `component_ids` is for `Self`
|
||||
func(ctx).read()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component> DynamicBundle for C {
|
||||
#[inline]
|
||||
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
|
||||
OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr));
|
||||
|
@ -203,7 +209,7 @@ macro_rules! tuple_impl {
|
|||
($($name: ident),*) => {
|
||||
// SAFETY:
|
||||
// - `Bundle::component_ids` calls `ids` for each component type in the
|
||||
// bundle, in the exact order that `Bundle::get_components` is called.
|
||||
// bundle, in the exact order that `DynamicBundle::get_components` is called.
|
||||
// - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`.
|
||||
// - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct
|
||||
// `StorageType` into the callback.
|
||||
|
@ -223,7 +229,9 @@ macro_rules! tuple_impl {
|
|||
// https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands
|
||||
($(<$name as Bundle>::from_components(ctx, func),)*)
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) {
|
||||
#[allow(unused_variables, unused_mut)]
|
||||
#[inline(always)]
|
||||
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
|
||||
|
@ -261,13 +269,62 @@ impl SparseSetIndex for BundleId {
|
|||
}
|
||||
|
||||
pub struct BundleInfo {
|
||||
pub(crate) id: BundleId,
|
||||
pub(crate) component_ids: Vec<ComponentId>,
|
||||
id: BundleId,
|
||||
// SAFETY: Every ID in this list must be valid within the World that owns the BundleInfo,
|
||||
// must have its storage initialized (i.e. columns created in tables, sparse set created),
|
||||
// and must be in the same order as the source bundle type writes its components in.
|
||||
component_ids: Vec<ComponentId>,
|
||||
}
|
||||
|
||||
impl BundleInfo {
|
||||
/// Create a new [`BundleInfo`].
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
// Every ID in `component_ids` must be valid within the World that owns the BundleInfo,
|
||||
// must have its storage initialized (i.e. columns created in tables, sparse set created),
|
||||
// and must be in the same order as the source bundle type writes its components in.
|
||||
unsafe fn new(
|
||||
bundle_type_name: &'static str,
|
||||
components: &Components,
|
||||
component_ids: Vec<ComponentId>,
|
||||
id: BundleId,
|
||||
) -> BundleInfo {
|
||||
let mut deduped = component_ids.clone();
|
||||
deduped.sort();
|
||||
deduped.dedup();
|
||||
|
||||
if deduped.len() != component_ids.len() {
|
||||
// TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized
|
||||
let mut seen = HashSet::new();
|
||||
let mut dups = Vec::new();
|
||||
for id in component_ids {
|
||||
if !seen.insert(id) {
|
||||
dups.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
let names = dups
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
// SAFETY: the caller ensures component_id is valid.
|
||||
unsafe { components.get_info_unchecked(id).name() }
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
panic!("Bundle {bundle_type_name} has duplicate components: {names}");
|
||||
}
|
||||
|
||||
// SAFETY: The caller ensures that component_ids:
|
||||
// - is valid for the associated world
|
||||
// - has had its storage initialized
|
||||
// - is in the same order as the source bundle type
|
||||
BundleInfo { id, component_ids }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> BundleId {
|
||||
pub const fn id(&self) -> BundleId {
|
||||
self.id
|
||||
}
|
||||
|
||||
|
@ -283,7 +340,7 @@ impl BundleInfo {
|
|||
components: &mut Components,
|
||||
storages: &'a mut Storages,
|
||||
archetype_id: ArchetypeId,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> BundleInserter<'a, 'b> {
|
||||
let new_archetype_id =
|
||||
self.add_bundle_to_archetype(archetypes, storages, components, archetype_id);
|
||||
|
@ -342,7 +399,7 @@ impl BundleInfo {
|
|||
archetypes: &'a mut Archetypes,
|
||||
components: &mut Components,
|
||||
storages: &'a mut Storages,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> BundleSpawner<'a, 'b> {
|
||||
let new_archetype_id =
|
||||
self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY);
|
||||
|
@ -376,14 +433,14 @@ impl BundleInfo {
|
|||
/// `entity`, `bundle` must match this [`BundleInfo`]'s type
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn write_components<T: Bundle, S: BundleComponentStatus>(
|
||||
unsafe fn write_components<T: DynamicBundle, S: BundleComponentStatus>(
|
||||
&self,
|
||||
table: &mut Table,
|
||||
sparse_sets: &mut SparseSets,
|
||||
bundle_component_status: &S,
|
||||
entity: Entity,
|
||||
table_row: TableRow,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
bundle: T,
|
||||
) {
|
||||
// NOTE: get_components calls this closure on each component in "bundle order".
|
||||
|
@ -393,11 +450,14 @@ impl BundleInfo {
|
|||
let component_id = *self.component_ids.get_unchecked(bundle_component);
|
||||
match storage_type {
|
||||
StorageType::Table => {
|
||||
let column = table.get_column_mut(component_id).unwrap();
|
||||
let column =
|
||||
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
|
||||
// the target table contains the component.
|
||||
unsafe { table.get_column_mut(component_id).debug_checked_unwrap() };
|
||||
// SAFETY: bundle_component is a valid index for this bundle
|
||||
match bundle_component_status.get_status(bundle_component) {
|
||||
ComponentStatus::Added => {
|
||||
column.initialize(table_row, component_ptr, Tick::new(change_tick));
|
||||
column.initialize(table_row, component_ptr, change_tick);
|
||||
}
|
||||
ComponentStatus::Mutated => {
|
||||
column.replace(table_row, component_ptr, change_tick);
|
||||
|
@ -405,11 +465,11 @@ impl BundleInfo {
|
|||
}
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
sparse_sets.get_mut(component_id).unwrap().insert(
|
||||
entity,
|
||||
component_ptr,
|
||||
change_tick,
|
||||
);
|
||||
let sparse_set =
|
||||
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
|
||||
// a sparse set exists for the component.
|
||||
unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() };
|
||||
sparse_set.insert(entity, component_ptr, change_tick);
|
||||
}
|
||||
}
|
||||
bundle_component += 1;
|
||||
|
@ -508,7 +568,7 @@ pub(crate) struct BundleInserter<'a, 'b> {
|
|||
sparse_sets: &'a mut SparseSets,
|
||||
result: InsertBundleResult<'a>,
|
||||
archetypes_ptr: *mut Archetype,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
}
|
||||
|
||||
pub(crate) enum InsertBundleResult<'a> {
|
||||
|
@ -527,7 +587,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
/// `entity` must currently exist in the source archetype for this inserter. `archetype_row`
|
||||
/// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type
|
||||
#[inline]
|
||||
pub unsafe fn insert<T: Bundle>(
|
||||
pub unsafe fn insert<T: DynamicBundle>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
|
@ -536,11 +596,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
match &mut self.result {
|
||||
InsertBundleResult::SameArchetype => {
|
||||
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
|
||||
let add_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(self.bundle_info.id)
|
||||
.unwrap();
|
||||
// SAFETY: The edge is assured to be initialized when creating the BundleInserter
|
||||
let add_bundle = unsafe {
|
||||
self.archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(self.bundle_info.id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
self.bundle_info.write_components(
|
||||
self.table,
|
||||
self.sparse_sets,
|
||||
|
@ -555,7 +617,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
|
||||
let result = self.archetype.swap_remove(location.archetype_row);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
let swapped_location = self.entities.get(swapped_entity).unwrap();
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
self.entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
|
@ -570,11 +634,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
self.entities.set(entity.index(), new_location);
|
||||
|
||||
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
|
||||
let add_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(self.bundle_info.id)
|
||||
.unwrap();
|
||||
// SAFETY: The edge is assured to be initialized when creating the BundleInserter
|
||||
let add_bundle = unsafe {
|
||||
self.archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(self.bundle_info.id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
self.bundle_info.write_components(
|
||||
self.table,
|
||||
self.sparse_sets,
|
||||
|
@ -592,7 +658,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
} => {
|
||||
let result = self.archetype.swap_remove(location.archetype_row);
|
||||
if let Some(swapped_entity) = result.swapped_entity {
|
||||
let swapped_location = self.entities.get(swapped_entity).unwrap();
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
self.entities.set(
|
||||
swapped_entity.index(),
|
||||
EntityLocation {
|
||||
|
@ -613,7 +681,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location = self.entities.get(swapped_entity).unwrap();
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() };
|
||||
let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id
|
||||
{
|
||||
&mut *self.archetype
|
||||
|
@ -640,11 +710,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
|||
}
|
||||
|
||||
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
|
||||
let add_bundle = self
|
||||
.archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(self.bundle_info.id)
|
||||
.unwrap();
|
||||
// SAFETY: The edge is assured to be initialized when creating the BundleInserter
|
||||
let add_bundle = unsafe {
|
||||
self.archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(self.bundle_info.id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
self.bundle_info.write_components(
|
||||
new_table,
|
||||
self.sparse_sets,
|
||||
|
@ -666,7 +738,7 @@ pub(crate) struct BundleSpawner<'a, 'b> {
|
|||
bundle_info: &'b BundleInfo,
|
||||
table: &'a mut Table,
|
||||
sparse_sets: &'a mut SparseSets,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
}
|
||||
|
||||
impl<'a, 'b> BundleSpawner<'a, 'b> {
|
||||
|
@ -677,7 +749,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
|
|||
/// # Safety
|
||||
/// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
|
||||
#[inline]
|
||||
pub unsafe fn spawn_non_existent<T: Bundle>(
|
||||
pub unsafe fn spawn_non_existent<T: DynamicBundle>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
bundle: T,
|
||||
|
@ -712,7 +784,12 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
|
|||
#[derive(Default)]
|
||||
pub struct Bundles {
|
||||
bundle_infos: Vec<BundleInfo>,
|
||||
/// Cache static [`BundleId`]
|
||||
bundle_ids: TypeIdMap<BundleId>,
|
||||
/// Cache dynamic [`BundleId`] with multiple components
|
||||
dynamic_bundle_ids: HashMap<Vec<ComponentId>, (BundleId, Vec<StorageType>)>,
|
||||
/// Cache optimized dynamic [`BundleId`] with single component
|
||||
dynamic_component_bundle_ids: HashMap<ComponentId, (BundleId, StorageType)>,
|
||||
}
|
||||
|
||||
impl Bundles {
|
||||
|
@ -726,6 +803,7 @@ impl Bundles {
|
|||
self.bundle_ids.get(&type_id).cloned()
|
||||
}
|
||||
|
||||
/// Initializes a new [`BundleInfo`] for a statically known type.
|
||||
pub(crate) fn init_info<'a, T: Bundle>(
|
||||
&'a mut self,
|
||||
components: &mut Components,
|
||||
|
@ -737,50 +815,98 @@ impl Bundles {
|
|||
T::component_ids(components, storages, &mut |id| component_ids.push(id));
|
||||
let id = BundleId(bundle_infos.len());
|
||||
let bundle_info =
|
||||
// SAFETY: T::component_id ensures info was created
|
||||
unsafe { initialize_bundle(std::any::type_name::<T>(), components, component_ids, id) };
|
||||
// SAFETY: T::component_id ensures its:
|
||||
// - info was created
|
||||
// - appropriate storage for it has been initialized.
|
||||
// - was created in the same order as the components in T
|
||||
unsafe { BundleInfo::new(std::any::type_name::<T>(), components, component_ids, id) };
|
||||
bundle_infos.push(bundle_info);
|
||||
id
|
||||
});
|
||||
// SAFETY: index either exists, or was initialized
|
||||
unsafe { self.bundle_infos.get_unchecked(id.0) }
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// `component_id` must be valid [`ComponentId`]'s
|
||||
unsafe fn initialize_bundle(
|
||||
bundle_type_name: &'static str,
|
||||
components: &Components,
|
||||
component_ids: Vec<ComponentId>,
|
||||
id: BundleId,
|
||||
) -> BundleInfo {
|
||||
let mut deduped = component_ids.clone();
|
||||
deduped.sort();
|
||||
deduped.dedup();
|
||||
/// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if any of the provided [`ComponentId`]s do not exist in the
|
||||
/// provided [`Components`].
|
||||
pub(crate) fn init_dynamic_info(
|
||||
&mut self,
|
||||
components: &mut Components,
|
||||
component_ids: &[ComponentId],
|
||||
) -> (&BundleInfo, &Vec<StorageType>) {
|
||||
let bundle_infos = &mut self.bundle_infos;
|
||||
|
||||
if deduped.len() != component_ids.len() {
|
||||
// TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized
|
||||
let mut seen = HashSet::new();
|
||||
let mut dups = Vec::new();
|
||||
for id in component_ids {
|
||||
if !seen.insert(id) {
|
||||
dups.push(id);
|
||||
}
|
||||
}
|
||||
// Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry`
|
||||
let (_, (bundle_id, storage_types)) = self
|
||||
.dynamic_bundle_ids
|
||||
.raw_entry_mut()
|
||||
.from_key(component_ids)
|
||||
.or_insert_with(|| {
|
||||
(
|
||||
Vec::from(component_ids),
|
||||
initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)),
|
||||
)
|
||||
});
|
||||
|
||||
let names = dups
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
// SAFETY: component_id exists and is therefore valid
|
||||
unsafe { components.get_info_unchecked(id).name() }
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
// SAFETY: index either exists, or was initialized
|
||||
let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
|
||||
|
||||
panic!("Bundle {bundle_type_name} has duplicate components: {names}");
|
||||
(bundle_info, storage_types)
|
||||
}
|
||||
|
||||
BundleInfo { id, component_ids }
|
||||
/// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the provided [`ComponentId`] does not exist in the provided [`Components`].
|
||||
pub(crate) fn init_component_info(
|
||||
&mut self,
|
||||
components: &mut Components,
|
||||
component_id: ComponentId,
|
||||
) -> (&BundleInfo, StorageType) {
|
||||
let bundle_infos = &mut self.bundle_infos;
|
||||
let (bundle_id, storage_types) = self
|
||||
.dynamic_component_bundle_ids
|
||||
.entry(component_id)
|
||||
.or_insert_with(|| {
|
||||
let (id, storage_type) =
|
||||
initialize_dynamic_bundle(bundle_infos, components, vec![component_id]);
|
||||
// SAFETY: `storage_type` guaranteed to have length 1
|
||||
(id, storage_type[0])
|
||||
});
|
||||
|
||||
// SAFETY: index either exists, or was initialized
|
||||
let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
|
||||
|
||||
(bundle_info, *storage_types)
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that all components are part of [`Components`]
|
||||
/// and initializes a [`BundleInfo`].
|
||||
fn initialize_dynamic_bundle(
|
||||
bundle_infos: &mut Vec<BundleInfo>,
|
||||
components: &Components,
|
||||
component_ids: Vec<ComponentId>,
|
||||
) -> (BundleId, Vec<StorageType>) {
|
||||
// Assert component existence
|
||||
let storage_types = component_ids.iter().map(|&id| {
|
||||
components.get_info(id).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"init_dynamic_info called with component id {id:?} which doesn't exist in this world"
|
||||
)
|
||||
}).storage_type()
|
||||
}).collect();
|
||||
|
||||
let id = BundleId(bundle_infos.len());
|
||||
let bundle_info =
|
||||
// SAFETY: `component_ids` are valid as they were just checked
|
||||
unsafe { BundleInfo::new("<dynamic bundle>", components, component_ids, id) };
|
||||
bundle_infos.push(bundle_info);
|
||||
|
||||
(id, storage_types)
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ pub trait DetectChanges {
|
|||
/// For comparison, the previous change tick of a system can be read using the
|
||||
/// [`SystemChangeTick`](crate::system::SystemChangeTick)
|
||||
/// [`SystemParam`](crate::system::SystemParam).
|
||||
fn last_changed(&self) -> u32;
|
||||
fn last_changed(&self) -> Tick;
|
||||
}
|
||||
|
||||
/// Types that implement reliable change detection.
|
||||
|
@ -109,7 +109,7 @@ pub trait DetectChangesMut: DetectChanges {
|
|||
/// This is a complex and error-prone operation, primarily intended for use with rollback networking strategies.
|
||||
/// If you merely want to flag this data as changed, use [`set_changed`](DetectChangesMut::set_changed) instead.
|
||||
/// If you want to avoid triggering change detection, use [`bypass_change_detection`](DetectChangesMut::bypass_change_detection) instead.
|
||||
fn set_last_changed(&mut self, last_change_tick: u32);
|
||||
fn set_last_changed(&mut self, last_changed: Tick);
|
||||
|
||||
/// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick.
|
||||
///
|
||||
|
@ -145,19 +145,19 @@ macro_rules! change_detection_impl {
|
|||
fn is_added(&self) -> bool {
|
||||
self.ticks
|
||||
.added
|
||||
.is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick)
|
||||
.is_newer_than(self.ticks.last_run, self.ticks.this_run)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_changed(&self) -> bool {
|
||||
self.ticks
|
||||
.changed
|
||||
.is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick)
|
||||
.is_newer_than(self.ticks.last_run, self.ticks.this_run)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn last_changed(&self) -> u32 {
|
||||
self.ticks.changed.tick
|
||||
fn last_changed(&self) -> Tick {
|
||||
*self.ticks.changed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,16 +186,12 @@ macro_rules! change_detection_mut_impl {
|
|||
|
||||
#[inline]
|
||||
fn set_changed(&mut self) {
|
||||
self.ticks
|
||||
.changed
|
||||
.set_changed(self.ticks.change_tick);
|
||||
*self.ticks.changed = self.ticks.this_run;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_last_changed(&mut self, last_changed: u32) {
|
||||
self.ticks
|
||||
.changed
|
||||
.set_changed(last_changed);
|
||||
fn set_last_changed(&mut self, last_changed: Tick) {
|
||||
*self.ticks.changed = last_changed;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -242,8 +238,8 @@ macro_rules! impl_methods {
|
|||
ticks: TicksMut {
|
||||
added: self.ticks.added,
|
||||
changed: self.ticks.changed,
|
||||
last_change_tick: self.ticks.last_change_tick,
|
||||
change_tick: self.ticks.change_tick,
|
||||
last_run: self.ticks.last_run,
|
||||
this_run: self.ticks.this_run,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,8 +295,8 @@ macro_rules! impl_debug {
|
|||
pub(crate) struct Ticks<'a> {
|
||||
pub(crate) added: &'a Tick,
|
||||
pub(crate) changed: &'a Tick,
|
||||
pub(crate) last_change_tick: u32,
|
||||
pub(crate) change_tick: u32,
|
||||
pub(crate) last_run: Tick,
|
||||
pub(crate) this_run: Tick,
|
||||
}
|
||||
|
||||
impl<'a> Ticks<'a> {
|
||||
|
@ -309,14 +305,14 @@ impl<'a> Ticks<'a> {
|
|||
#[inline]
|
||||
pub(crate) unsafe fn from_tick_cells(
|
||||
cells: TickCells<'a>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
Self {
|
||||
added: cells.added.deref(),
|
||||
changed: cells.changed.deref(),
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -324,8 +320,8 @@ impl<'a> Ticks<'a> {
|
|||
pub(crate) struct TicksMut<'a> {
|
||||
pub(crate) added: &'a mut Tick,
|
||||
pub(crate) changed: &'a mut Tick,
|
||||
pub(crate) last_change_tick: u32,
|
||||
pub(crate) change_tick: u32,
|
||||
pub(crate) last_run: Tick,
|
||||
pub(crate) this_run: Tick,
|
||||
}
|
||||
|
||||
impl<'a> TicksMut<'a> {
|
||||
|
@ -334,14 +330,14 @@ impl<'a> TicksMut<'a> {
|
|||
#[inline]
|
||||
pub(crate) unsafe fn from_tick_cells(
|
||||
cells: TickCells<'a>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
Self {
|
||||
added: cells.added.deref_mut(),
|
||||
changed: cells.changed.deref_mut(),
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -351,8 +347,8 @@ impl<'a> From<TicksMut<'a>> for Ticks<'a> {
|
|||
Ticks {
|
||||
added: ticks.added,
|
||||
changed: ticks.changed,
|
||||
last_change_tick: ticks.last_change_tick,
|
||||
change_tick: ticks.change_tick,
|
||||
last_run: ticks.last_run,
|
||||
this_run: ticks.this_run,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -537,6 +533,41 @@ pub struct Mut<'a, T: ?Sized> {
|
|||
pub(crate) ticks: TicksMut<'a>,
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Mut<'a, T> {
|
||||
/// Creates a new change-detection enabled smart pointer.
|
||||
/// In almost all cases you do not need to call this method manually,
|
||||
/// as instances of `Mut` will be created by engine-internal code.
|
||||
///
|
||||
/// Many use-cases of this method would be better served by [`Mut::map_unchanged`]
|
||||
/// or [`Mut::reborrow`].
|
||||
///
|
||||
/// - `value` - The value wrapped by this smart pointer.
|
||||
/// - `added` - A [`Tick`] that stores the tick when the wrapped value was created.
|
||||
/// - `last_changed` - A [`Tick`] that stores the last time the wrapped value was changed.
|
||||
/// This will be updated to the value of `change_tick` if the returned smart pointer
|
||||
/// is modified.
|
||||
/// - `last_run` - A [`Tick`], occurring before `this_run`, which is used
|
||||
/// as a reference to determine whether the wrapped value is newly added or changed.
|
||||
/// - `this_run` - A [`Tick`] corresponding to the current point in time -- "now".
|
||||
pub fn new(
|
||||
value: &'a mut T,
|
||||
added: &'a mut Tick,
|
||||
last_changed: &'a mut Tick,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
Self {
|
||||
value,
|
||||
ticks: TicksMut {
|
||||
added,
|
||||
changed: last_changed,
|
||||
last_run,
|
||||
this_run,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> From<Mut<'a, T>> for Ref<'a, T> {
|
||||
fn from(mut_ref: Mut<'a, T>) -> Self {
|
||||
Self {
|
||||
|
@ -608,8 +639,8 @@ impl<'a> MutUntyped<'a> {
|
|||
ticks: TicksMut {
|
||||
added: self.ticks.added,
|
||||
changed: self.ticks.changed,
|
||||
last_change_tick: self.ticks.last_change_tick,
|
||||
change_tick: self.ticks.change_tick,
|
||||
last_run: self.ticks.last_run,
|
||||
this_run: self.ticks.this_run,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -646,19 +677,19 @@ impl<'a> DetectChanges for MutUntyped<'a> {
|
|||
fn is_added(&self) -> bool {
|
||||
self.ticks
|
||||
.added
|
||||
.is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick)
|
||||
.is_newer_than(self.ticks.last_run, self.ticks.this_run)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_changed(&self) -> bool {
|
||||
self.ticks
|
||||
.changed
|
||||
.is_newer_than(self.ticks.last_change_tick, self.ticks.change_tick)
|
||||
.is_newer_than(self.ticks.last_run, self.ticks.this_run)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn last_changed(&self) -> u32 {
|
||||
self.ticks.changed.tick
|
||||
fn last_changed(&self) -> Tick {
|
||||
*self.ticks.changed
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -667,12 +698,12 @@ impl<'a> DetectChangesMut for MutUntyped<'a> {
|
|||
|
||||
#[inline]
|
||||
fn set_changed(&mut self) {
|
||||
self.ticks.changed.set_changed(self.ticks.change_tick);
|
||||
*self.ticks.changed = self.ticks.this_run;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_last_changed(&mut self, last_changed: u32) {
|
||||
self.ticks.changed.set_changed(last_changed);
|
||||
fn set_last_changed(&mut self, last_changed: Tick) {
|
||||
*self.ticks.changed = last_changed;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -757,7 +788,7 @@ mod tests {
|
|||
}
|
||||
|
||||
let mut world = World::new();
|
||||
world.last_change_tick = u32::MAX;
|
||||
world.last_change_tick = Tick::new(u32::MAX);
|
||||
*world.change_tick.get_mut() = 0;
|
||||
|
||||
// component added: 0, changed: 0
|
||||
|
@ -785,8 +816,8 @@ mod tests {
|
|||
|
||||
let mut query = world.query::<Ref<C>>();
|
||||
for tracker in query.iter(&world) {
|
||||
let ticks_since_insert = change_tick.wrapping_sub(tracker.ticks.added.tick);
|
||||
let ticks_since_change = change_tick.wrapping_sub(tracker.ticks.changed.tick);
|
||||
let ticks_since_insert = change_tick.relative_to(*tracker.ticks.added).get();
|
||||
let ticks_since_change = change_tick.relative_to(*tracker.ticks.changed).get();
|
||||
assert!(ticks_since_insert > MAX_CHANGE_AGE);
|
||||
assert!(ticks_since_change > MAX_CHANGE_AGE);
|
||||
}
|
||||
|
@ -795,8 +826,8 @@ mod tests {
|
|||
world.check_change_ticks();
|
||||
|
||||
for tracker in query.iter(&world) {
|
||||
let ticks_since_insert = change_tick.wrapping_sub(tracker.ticks.added.tick);
|
||||
let ticks_since_change = change_tick.wrapping_sub(tracker.ticks.changed.tick);
|
||||
let ticks_since_insert = change_tick.relative_to(*tracker.ticks.added).get();
|
||||
let ticks_since_change = change_tick.relative_to(*tracker.ticks.changed).get();
|
||||
assert!(ticks_since_insert == MAX_CHANGE_AGE);
|
||||
assert!(ticks_since_change == MAX_CHANGE_AGE);
|
||||
}
|
||||
|
@ -811,8 +842,8 @@ mod tests {
|
|||
let ticks = TicksMut {
|
||||
added: &mut component_ticks.added,
|
||||
changed: &mut component_ticks.changed,
|
||||
last_change_tick: 3,
|
||||
change_tick: 4,
|
||||
last_run: Tick::new(3),
|
||||
this_run: Tick::new(4),
|
||||
};
|
||||
let mut res = R {};
|
||||
let res_mut = ResMut {
|
||||
|
@ -821,10 +852,30 @@ mod tests {
|
|||
};
|
||||
|
||||
let into_mut: Mut<R> = res_mut.into();
|
||||
assert_eq!(1, into_mut.ticks.added.tick);
|
||||
assert_eq!(2, into_mut.ticks.changed.tick);
|
||||
assert_eq!(3, into_mut.ticks.last_change_tick);
|
||||
assert_eq!(4, into_mut.ticks.change_tick);
|
||||
assert_eq!(1, into_mut.ticks.added.get());
|
||||
assert_eq!(2, into_mut.ticks.changed.get());
|
||||
assert_eq!(3, into_mut.ticks.last_run.get());
|
||||
assert_eq!(4, into_mut.ticks.this_run.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mut_new() {
|
||||
let mut component_ticks = ComponentTicks {
|
||||
added: Tick::new(1),
|
||||
changed: Tick::new(3),
|
||||
};
|
||||
let mut res = R {};
|
||||
|
||||
let val = Mut::new(
|
||||
&mut res,
|
||||
&mut component_ticks.added,
|
||||
&mut component_ticks.changed,
|
||||
Tick::new(2), // last_run
|
||||
Tick::new(4), // this_run
|
||||
);
|
||||
|
||||
assert!(!val.is_added());
|
||||
assert!(val.is_changed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -836,8 +887,8 @@ mod tests {
|
|||
let ticks = TicksMut {
|
||||
added: &mut component_ticks.added,
|
||||
changed: &mut component_ticks.changed,
|
||||
last_change_tick: 3,
|
||||
change_tick: 4,
|
||||
last_run: Tick::new(3),
|
||||
this_run: Tick::new(4),
|
||||
};
|
||||
let mut res = R {};
|
||||
let non_send_mut = NonSendMut {
|
||||
|
@ -846,10 +897,10 @@ mod tests {
|
|||
};
|
||||
|
||||
let into_mut: Mut<R> = non_send_mut.into();
|
||||
assert_eq!(1, into_mut.ticks.added.tick);
|
||||
assert_eq!(2, into_mut.ticks.changed.tick);
|
||||
assert_eq!(3, into_mut.ticks.last_change_tick);
|
||||
assert_eq!(4, into_mut.ticks.change_tick);
|
||||
assert_eq!(1, into_mut.ticks.added.get());
|
||||
assert_eq!(2, into_mut.ticks.changed.get());
|
||||
assert_eq!(3, into_mut.ticks.last_run.get());
|
||||
assert_eq!(4, into_mut.ticks.this_run.get());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -857,7 +908,8 @@ mod tests {
|
|||
use super::*;
|
||||
struct Outer(i64);
|
||||
|
||||
let (last_change_tick, change_tick) = (2, 3);
|
||||
let last_run = Tick::new(2);
|
||||
let this_run = Tick::new(3);
|
||||
let mut component_ticks = ComponentTicks {
|
||||
added: Tick::new(1),
|
||||
changed: Tick::new(2),
|
||||
|
@ -865,8 +917,8 @@ mod tests {
|
|||
let ticks = TicksMut {
|
||||
added: &mut component_ticks.added,
|
||||
changed: &mut component_ticks.changed,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
};
|
||||
|
||||
let mut outer = Outer(0);
|
||||
|
@ -884,7 +936,7 @@ mod tests {
|
|||
*inner = 64;
|
||||
assert!(inner.is_changed());
|
||||
// Modifying one field of a component should flag a change for the entire component.
|
||||
assert!(component_ticks.is_changed(last_change_tick, change_tick));
|
||||
assert!(component_ticks.is_changed(last_run, this_run));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -586,61 +586,72 @@ impl Components {
|
|||
}
|
||||
}
|
||||
|
||||
/// Used to track changes in state between system runs, e.g. components being added or accessed mutably.
|
||||
/// A value that tracks when a system ran relative to other systems.
|
||||
/// This is used to power change detection.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Tick {
|
||||
pub(crate) tick: u32,
|
||||
tick: u32,
|
||||
}
|
||||
|
||||
impl Tick {
|
||||
/// The maximum relative age for a change tick.
|
||||
/// The value of this is equal to [`crate::change_detection::MAX_CHANGE_AGE`].
|
||||
///
|
||||
/// Since change detection will not work for any ticks older than this,
|
||||
/// ticks are periodically scanned to ensure their relative values are below this.
|
||||
pub const MAX: Self = Self::new(MAX_CHANGE_AGE);
|
||||
|
||||
pub const fn new(tick: u32) -> Self {
|
||||
Self { tick }
|
||||
}
|
||||
|
||||
/// Gets the value of this change tick.
|
||||
#[inline]
|
||||
/// Returns `true` if this `Tick` occurred since the system's `last_change_tick`.
|
||||
pub const fn get(self) -> u32 {
|
||||
self.tick
|
||||
}
|
||||
|
||||
/// Sets the value of this change tick.
|
||||
#[inline]
|
||||
pub fn set(&mut self, tick: u32) {
|
||||
self.tick = tick;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns `true` if this `Tick` occurred since the system's `last_run`.
|
||||
///
|
||||
/// `change_tick` is the current tick of the system, used as a reference to help deal with wraparound.
|
||||
pub fn is_newer_than(&self, last_change_tick: u32, change_tick: u32) -> bool {
|
||||
// This works even with wraparound because the world tick (`change_tick`) is always "newer" than
|
||||
// `last_change_tick` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
|
||||
/// `this_run` is the current tick of the system, used as a reference to help deal with wraparound.
|
||||
pub fn is_newer_than(self, last_run: Tick, this_run: Tick) -> bool {
|
||||
// This works even with wraparound because the world tick (`this_run`) is always "newer" than
|
||||
// `last_run` and `self.tick`, and we scan periodically to clamp `ComponentTicks` values
|
||||
// so they never get older than `u32::MAX` (the difference would overflow).
|
||||
//
|
||||
// The clamp here ensures determinism (since scans could differ between app runs).
|
||||
let ticks_since_insert = change_tick.wrapping_sub(self.tick).min(MAX_CHANGE_AGE);
|
||||
let ticks_since_system = change_tick
|
||||
.wrapping_sub(last_change_tick)
|
||||
.min(MAX_CHANGE_AGE);
|
||||
let ticks_since_insert = this_run.relative_to(self).tick.min(MAX_CHANGE_AGE);
|
||||
let ticks_since_system = this_run.relative_to(last_run).tick.min(MAX_CHANGE_AGE);
|
||||
|
||||
ticks_since_system > ticks_since_insert
|
||||
}
|
||||
|
||||
pub(crate) fn check_tick(&mut self, change_tick: u32) {
|
||||
let age = change_tick.wrapping_sub(self.tick);
|
||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
||||
// so long as this check always runs before that can happen.
|
||||
if age > MAX_CHANGE_AGE {
|
||||
self.tick = change_tick.wrapping_sub(MAX_CHANGE_AGE);
|
||||
}
|
||||
/// Returns a change tick representing the relationship between `self` and `other`.
|
||||
pub(crate) fn relative_to(self, other: Self) -> Self {
|
||||
let tick = self.tick.wrapping_sub(other.tick);
|
||||
Self { tick }
|
||||
}
|
||||
|
||||
/// Manually sets the change tick.
|
||||
/// Wraps this change tick's value if it exceeds [`Tick::MAX`].
|
||||
///
|
||||
/// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation
|
||||
/// on [`Mut<T>`](crate::change_detection::Mut), [`ResMut<T>`](crate::change_detection::ResMut), etc.
|
||||
/// However, components and resources that make use of interior mutability might require manual updates.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,no_run
|
||||
/// # use bevy_ecs::{world::World, component::ComponentTicks};
|
||||
/// let world: World = unimplemented!();
|
||||
/// let component_ticks: ComponentTicks = unimplemented!();
|
||||
///
|
||||
/// component_ticks.set_changed(world.read_change_tick());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn set_changed(&mut self, change_tick: u32) {
|
||||
self.tick = change_tick;
|
||||
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
||||
pub(crate) fn check_tick(&mut self, tick: Tick) -> bool {
|
||||
let age = tick.relative_to(*self);
|
||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
||||
// so long as this check always runs before that can happen.
|
||||
if age.get() > Self::MAX.get() {
|
||||
*self = tick.relative_to(Self::MAX);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -673,20 +684,20 @@ pub struct ComponentTicks {
|
|||
impl ComponentTicks {
|
||||
#[inline]
|
||||
/// Returns `true` if the component was added after the system last ran.
|
||||
pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool {
|
||||
self.added.is_newer_than(last_change_tick, change_tick)
|
||||
pub fn is_added(&self, last_run: Tick, this_run: Tick) -> bool {
|
||||
self.added.is_newer_than(last_run, this_run)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Returns `true` if the component was added or mutably dereferenced after the system last ran.
|
||||
pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool {
|
||||
self.changed.is_newer_than(last_change_tick, change_tick)
|
||||
pub fn is_changed(&self, last_run: Tick, this_run: Tick) -> bool {
|
||||
self.changed.is_newer_than(last_run, this_run)
|
||||
}
|
||||
|
||||
pub(crate) fn new(change_tick: u32) -> Self {
|
||||
pub(crate) fn new(change_tick: Tick) -> Self {
|
||||
Self {
|
||||
added: Tick::new(change_tick),
|
||||
changed: Tick::new(change_tick),
|
||||
added: change_tick,
|
||||
changed: change_tick,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -705,8 +716,8 @@ impl ComponentTicks {
|
|||
/// component_ticks.set_changed(world.read_change_tick());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn set_changed(&mut self, change_tick: u32) {
|
||||
self.changed.set_changed(change_tick);
|
||||
pub fn set_changed(&mut self, change_tick: Tick) {
|
||||
self.changed = change_tick;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -571,8 +571,7 @@ impl Entities {
|
|||
/// Returns the location of an [`Entity`].
|
||||
/// Note: for pending entities, returns `Some(EntityLocation::INVALID)`.
|
||||
pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
|
||||
if (entity.index as usize) < self.meta.len() {
|
||||
let meta = &self.meta[entity.index as usize];
|
||||
if let Some(meta) = self.meta.get(entity.index as usize) {
|
||||
if meta.generation != entity.generation
|
||||
|| meta.location.archetype_id == ArchetypeId::INVALID
|
||||
{
|
||||
|
|
|
@ -939,7 +939,7 @@ mod tests {
|
|||
world.send_event(TestEvent { i: 4 });
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(|mut events: EventReader<TestEvent>| {
|
||||
schedule.add_systems(|mut events: EventReader<TestEvent>| {
|
||||
let mut iter = events.iter();
|
||||
|
||||
assert_eq!(iter.next(), Some(&TestEvent { i: 0 }));
|
||||
|
|
|
@ -25,9 +25,6 @@ pub use bevy_ptr as ptr;
|
|||
|
||||
/// Most commonly used re-exported types.
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
#[allow(deprecated)]
|
||||
pub use crate::query::ChangeTrackers;
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub use crate::reflect::{ReflectComponent, ReflectResource};
|
||||
|
@ -42,9 +39,8 @@ pub mod prelude {
|
|||
removal_detection::RemovedComponents,
|
||||
schedule::{
|
||||
apply_state_transition, apply_system_buffers, common_conditions::*, Condition,
|
||||
IntoSystemConfig, IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig,
|
||||
IntoSystemSetConfigs, NextState, OnEnter, OnExit, OnUpdate, Schedule, Schedules, State,
|
||||
States, SystemSet,
|
||||
IntoSystemConfigs, IntoSystemSet, IntoSystemSetConfig, IntoSystemSetConfigs, NextState,
|
||||
OnEnter, OnExit, OnTransition, OnUpdate, Schedule, Schedules, State, States, SystemSet,
|
||||
},
|
||||
system::{
|
||||
adapter as system_adapter,
|
||||
|
@ -1299,33 +1295,6 @@ mod tests {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn trackers_query() {
|
||||
use crate::prelude::ChangeTrackers;
|
||||
|
||||
let mut world = World::default();
|
||||
let e1 = world.spawn((A(0), B(0))).id();
|
||||
world.spawn(B(0));
|
||||
|
||||
let mut trackers_query = world.query::<Option<ChangeTrackers<A>>>();
|
||||
let trackers = trackers_query.iter(&world).collect::<Vec<_>>();
|
||||
let a_trackers = trackers[0].as_ref().unwrap();
|
||||
assert!(trackers[1].is_none());
|
||||
assert!(a_trackers.is_added());
|
||||
assert!(a_trackers.is_changed());
|
||||
world.clear_trackers();
|
||||
let trackers = trackers_query.iter(&world).collect::<Vec<_>>();
|
||||
let a_trackers = trackers[0].as_ref().unwrap();
|
||||
assert!(!a_trackers.is_added());
|
||||
assert!(!a_trackers.is_changed());
|
||||
*world.get_mut(e1).unwrap() = A(1);
|
||||
let trackers = trackers_query.iter(&world).collect::<Vec<_>>();
|
||||
let a_trackers = trackers[0].as_ref().unwrap();
|
||||
assert!(!a_trackers.is_added());
|
||||
assert!(a_trackers.is_changed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_size_query() {
|
||||
let mut world = World::default();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId},
|
||||
change_detection::{Ticks, TicksMut},
|
||||
component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick},
|
||||
component::{Component, ComponentId, ComponentStorage, StorageType, Tick},
|
||||
entity::Entity,
|
||||
query::{Access, DebugCheckedUnwrap, FilteredAccess},
|
||||
storage::{ComponentSparseSet, Table, TableRow},
|
||||
|
@ -37,7 +37,7 @@ use std::{cell::UnsafeCell, marker::PhantomData};
|
|||
/// Wrapping it into an `Option` will increase the query search space, and it will return `None` if an entity doesn't satisfy the `WorldQuery`.
|
||||
/// - **[`AnyOf`].**
|
||||
/// Equivalent to wrapping each world query inside it into an `Option`.
|
||||
/// - **[`ChangeTrackers`].**
|
||||
/// - **[`Ref`].**
|
||||
/// Similar to change detection filters but it is used as a query fetch parameter.
|
||||
/// It exposes methods to check for changes to the wrapped component.
|
||||
///
|
||||
|
@ -92,16 +92,13 @@ use std::{cell::UnsafeCell, marker::PhantomData};
|
|||
///
|
||||
/// ## Macro expansion
|
||||
///
|
||||
/// Expanding the macro will declare three or six additional structs, depending on whether or not the struct is marked as mutable.
|
||||
/// Expanding the macro will declare one or three additional structs, depending on whether or not the struct is marked as mutable.
|
||||
/// For a struct named `X`, the additional structs will be:
|
||||
///
|
||||
/// |Struct name|`mutable` only|Description|
|
||||
/// |:---:|:---:|---|
|
||||
/// |`XState`|---|Used as the [`State`] type for `X` and `XReadOnly`|
|
||||
/// |`XItem`|---|The type of the query item for `X`|
|
||||
/// |`XFetch`|---|Used as the [`Fetch`] type for `X`|
|
||||
/// |`XReadOnlyItem`|✓|The type of the query item for `XReadOnly`|
|
||||
/// |`XReadOnlyFetch`|✓|Used as the [`Fetch`] type for `XReadOnly`|
|
||||
/// |`XReadOnly`|✓|[`ReadOnly`] variant of `X`|
|
||||
///
|
||||
/// ## Adding mutable references
|
||||
|
@ -296,7 +293,6 @@ use std::{cell::UnsafeCell, marker::PhantomData};
|
|||
/// [`Added`]: crate::query::Added
|
||||
/// [`fetch`]: Self::fetch
|
||||
/// [`Changed`]: crate::query::Changed
|
||||
/// [`Fetch`]: crate::query::WorldQuery::Fetch
|
||||
/// [`matches_component_set`]: Self::matches_component_set
|
||||
/// [`Or`]: crate::query::Or
|
||||
/// [`Query`]: crate::system::Query
|
||||
|
@ -333,8 +329,8 @@ pub unsafe trait WorldQuery {
|
|||
unsafe fn init_fetch<'w>(
|
||||
world: &'w World,
|
||||
state: &Self::State,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self::Fetch<'w>;
|
||||
|
||||
/// While this function can be called for any query, it is always safe to call if `Self: ReadOnlyWorldQuery` holds.
|
||||
|
@ -464,8 +460,8 @@ unsafe impl WorldQuery for Entity {
|
|||
unsafe fn init_fetch<'w>(
|
||||
_world: &'w World,
|
||||
_state: &Self::State,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32,
|
||||
_last_run: Tick,
|
||||
_this_run: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
}
|
||||
|
||||
|
@ -546,8 +542,8 @@ unsafe impl<T: Component> WorldQuery for &T {
|
|||
unsafe fn init_fetch<'w>(
|
||||
world: &'w World,
|
||||
&component_id: &ComponentId,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32,
|
||||
_last_run: Tick,
|
||||
_this_run: Tick,
|
||||
) -> ReadFetch<'w, T> {
|
||||
ReadFetch {
|
||||
table_components: None,
|
||||
|
@ -664,8 +660,8 @@ pub struct RefFetch<'w, T> {
|
|||
// T::Storage = SparseStorage
|
||||
sparse_set: Option<&'w ComponentSparseSet>,
|
||||
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
}
|
||||
|
||||
/// SAFETY: `Self` is the same as `Self::ReadOnly`
|
||||
|
@ -691,8 +687,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
|||
unsafe fn init_fetch<'w>(
|
||||
world: &'w World,
|
||||
&component_id: &ComponentId,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> RefFetch<'w, T> {
|
||||
RefFetch {
|
||||
table_data: None,
|
||||
|
@ -703,8 +699,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
|||
.get(component_id)
|
||||
.debug_checked_unwrap()
|
||||
}),
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -712,8 +708,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
|||
RefFetch {
|
||||
table_data: fetch.table_data,
|
||||
sparse_set: fetch.sparse_set,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
change_tick: fetch.change_tick,
|
||||
last_run: fetch.last_run,
|
||||
this_run: fetch.this_run,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -758,8 +754,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
|||
ticks: Ticks {
|
||||
added: added_ticks.get(table_row.index()).deref(),
|
||||
changed: changed_ticks.get(table_row.index()).deref(),
|
||||
change_tick: fetch.change_tick,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
this_run: fetch.this_run,
|
||||
last_run: fetch.last_run,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -771,7 +767,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
|
|||
.debug_checked_unwrap();
|
||||
Ref {
|
||||
value: component.deref(),
|
||||
ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick),
|
||||
ticks: Ticks::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -825,8 +821,8 @@ pub struct WriteFetch<'w, T> {
|
|||
// T::Storage = SparseStorage
|
||||
sparse_set: Option<&'w ComponentSparseSet>,
|
||||
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
}
|
||||
|
||||
/// SAFETY: access of `&T` is a subset of `&mut T`
|
||||
|
@ -852,8 +848,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
|||
unsafe fn init_fetch<'w>(
|
||||
world: &'w World,
|
||||
&component_id: &ComponentId,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> WriteFetch<'w, T> {
|
||||
WriteFetch {
|
||||
table_data: None,
|
||||
|
@ -864,8 +860,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
|||
.get(component_id)
|
||||
.debug_checked_unwrap()
|
||||
}),
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -873,8 +869,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
|||
WriteFetch {
|
||||
table_data: fetch.table_data,
|
||||
sparse_set: fetch.sparse_set,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
change_tick: fetch.change_tick,
|
||||
last_run: fetch.last_run,
|
||||
this_run: fetch.this_run,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -919,8 +915,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
|||
ticks: TicksMut {
|
||||
added: added_ticks.get(table_row.index()).deref_mut(),
|
||||
changed: changed_ticks.get(table_row.index()).deref_mut(),
|
||||
change_tick: fetch.change_tick,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
this_run: fetch.this_run,
|
||||
last_run: fetch.last_run,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -932,11 +928,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
|
|||
.debug_checked_unwrap();
|
||||
Mut {
|
||||
value: component.assert_unique().deref_mut(),
|
||||
ticks: TicksMut::from_tick_cells(
|
||||
ticks,
|
||||
fetch.last_change_tick,
|
||||
fetch.change_tick,
|
||||
),
|
||||
ticks: TicksMut::from_tick_cells(ticks, fetch.last_run, fetch.this_run),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1000,11 +992,11 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
|||
unsafe fn init_fetch<'w>(
|
||||
world: &'w World,
|
||||
state: &T::State,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> OptionFetch<'w, T> {
|
||||
OptionFetch {
|
||||
fetch: T::init_fetch(world, state, last_change_tick, change_tick),
|
||||
fetch: T::init_fetch(world, state, last_run, this_run),
|
||||
matches: false,
|
||||
}
|
||||
}
|
||||
|
@ -1083,246 +1075,6 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
|||
/// SAFETY: [`OptionFetch`] is read only because `T` is read only
|
||||
unsafe impl<T: ReadOnlyWorldQuery> ReadOnlyWorldQuery for Option<T> {}
|
||||
|
||||
/// [`WorldQuery`] that tracks changes and additions for component `T`.
|
||||
///
|
||||
/// Wraps a [`Component`] to track whether the component changed for the corresponding entities in
|
||||
/// a query since the last time the system that includes these queries ran.
|
||||
///
|
||||
/// If you only care about entities that changed or that got added use the
|
||||
/// [`Changed`](crate::query::Changed) and [`Added`](crate::query::Added) filters instead.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::query::ChangeTrackers;
|
||||
/// # use bevy_ecs::system::IntoSystem;
|
||||
/// # use bevy_ecs::system::Query;
|
||||
/// #
|
||||
/// # #[derive(Component, Debug)]
|
||||
/// # struct Name {};
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Transform {};
|
||||
/// #
|
||||
/// fn print_moving_objects_system(query: Query<(&Name, ChangeTrackers<Transform>)>) {
|
||||
/// for (name, tracker) in &query {
|
||||
/// if tracker.is_changed() {
|
||||
/// println!("Entity moved: {:?}", name);
|
||||
/// } else {
|
||||
/// println!("Entity stood still: {:?}", name);
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(print_moving_objects_system);
|
||||
/// ```
|
||||
#[deprecated = "`ChangeTrackers<T>` will be removed in bevy 0.11. Use `bevy_ecs::prelude::Ref<T>` instead."]
|
||||
pub struct ChangeTrackers<T: Component> {
|
||||
pub(crate) component_ticks: ComponentTicks,
|
||||
pub(crate) last_change_tick: u32,
|
||||
pub(crate) change_tick: u32,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Component> Clone for ChangeTrackers<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Component> Copy for ChangeTrackers<T> {}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Component> std::fmt::Debug for ChangeTrackers<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ChangeTrackers")
|
||||
.field("component_ticks", &self.component_ticks)
|
||||
.field("last_change_tick", &self.last_change_tick)
|
||||
.field("change_tick", &self.change_tick)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Component> ChangeTrackers<T> {
|
||||
/// Returns true if this component has been added since the last execution of this system.
|
||||
pub fn is_added(&self) -> bool {
|
||||
self.component_ticks
|
||||
.is_added(self.last_change_tick, self.change_tick)
|
||||
}
|
||||
|
||||
/// Returns true if this component has been changed since the last execution of this system.
|
||||
pub fn is_changed(&self) -> bool {
|
||||
self.component_ticks
|
||||
.is_changed(self.last_change_tick, self.change_tick)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct ChangeTrackersFetch<'w, T> {
|
||||
// T::Storage = TableStorage
|
||||
table_added: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
||||
table_changed: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
||||
// T::Storage = SparseStorage
|
||||
sparse_set: Option<&'w ComponentSparseSet>,
|
||||
|
||||
marker: PhantomData<T>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
// SAFETY: `ROQueryFetch<Self>` is the same as `QueryFetch<Self>`
|
||||
unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
|
||||
type Fetch<'w> = ChangeTrackersFetch<'w, T>;
|
||||
type Item<'w> = ChangeTrackers<T>;
|
||||
type ReadOnly = Self;
|
||||
type State = ComponentId;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
|
||||
item
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = {
|
||||
match T::Storage::STORAGE_TYPE {
|
||||
StorageType::Table => true,
|
||||
StorageType::SparseSet => false,
|
||||
}
|
||||
};
|
||||
|
||||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
unsafe fn init_fetch<'w>(
|
||||
world: &'w World,
|
||||
&component_id: &ComponentId,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
) -> ChangeTrackersFetch<'w, T> {
|
||||
ChangeTrackersFetch {
|
||||
table_added: None,
|
||||
table_changed: None,
|
||||
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| {
|
||||
world
|
||||
.storages()
|
||||
.sparse_sets
|
||||
.get(component_id)
|
||||
.debug_checked_unwrap()
|
||||
}),
|
||||
marker: PhantomData,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
|
||||
ChangeTrackersFetch {
|
||||
table_added: fetch.table_added,
|
||||
table_changed: fetch.table_changed,
|
||||
sparse_set: fetch.sparse_set,
|
||||
marker: fetch.marker,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
change_tick: fetch.change_tick,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut ChangeTrackersFetch<'w, T>,
|
||||
component_id: &ComponentId,
|
||||
_archetype: &'w Archetype,
|
||||
table: &'w Table,
|
||||
) {
|
||||
if Self::IS_DENSE {
|
||||
Self::set_table(fetch, component_id, table);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn set_table<'w>(
|
||||
fetch: &mut ChangeTrackersFetch<'w, T>,
|
||||
&id: &ComponentId,
|
||||
table: &'w Table,
|
||||
) {
|
||||
let column = table.get_column(id).debug_checked_unwrap();
|
||||
fetch.table_added = Some(column.get_added_ticks_slice().into());
|
||||
fetch.table_changed = Some(column.get_changed_ticks_slice().into());
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn fetch<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
table_row: TableRow,
|
||||
) -> Self::Item<'w> {
|
||||
match T::Storage::STORAGE_TYPE {
|
||||
StorageType::Table => ChangeTrackers {
|
||||
component_ticks: {
|
||||
ComponentTicks {
|
||||
added: fetch
|
||||
.table_added
|
||||
.debug_checked_unwrap()
|
||||
.get(table_row.index())
|
||||
.read(),
|
||||
changed: fetch
|
||||
.table_changed
|
||||
.debug_checked_unwrap()
|
||||
.get(table_row.index())
|
||||
.read(),
|
||||
}
|
||||
},
|
||||
marker: PhantomData,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
change_tick: fetch.change_tick,
|
||||
},
|
||||
StorageType::SparseSet => ChangeTrackers {
|
||||
component_ticks: fetch
|
||||
.sparse_set
|
||||
.debug_checked_unwrap()
|
||||
.get_ticks(entity)
|
||||
.debug_checked_unwrap(),
|
||||
marker: PhantomData,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
change_tick: fetch.change_tick,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) {
|
||||
assert!(
|
||||
!access.access().has_write(id),
|
||||
"ChangeTrackers<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",
|
||||
std::any::type_name::<T>()
|
||||
);
|
||||
access.add_read(id);
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(
|
||||
&id: &ComponentId,
|
||||
archetype: &Archetype,
|
||||
access: &mut Access<ArchetypeComponentId>,
|
||||
) {
|
||||
if let Some(archetype_component_id) = archetype.get_archetype_component_id(id) {
|
||||
access.add_read(archetype_component_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> ComponentId {
|
||||
world.init_component::<T>()
|
||||
}
|
||||
|
||||
fn matches_component_set(
|
||||
&id: &ComponentId,
|
||||
set_contains_id: &impl Fn(ComponentId) -> bool,
|
||||
) -> bool {
|
||||
set_contains_id(id)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
/// SAFETY: access is read only
|
||||
unsafe impl<T: Component> ReadOnlyWorldQuery for ChangeTrackers<T> {}
|
||||
|
||||
macro_rules! impl_tuple_fetch {
|
||||
($(($name: ident, $state: ident)),*) => {
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -1342,9 +1094,9 @@ macro_rules! impl_tuple_fetch {
|
|||
}
|
||||
|
||||
#[allow(clippy::unused_unit)]
|
||||
unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> {
|
||||
unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> {
|
||||
let ($($name,)*) = state;
|
||||
($($name::init_fetch(_world, $name, _last_change_tick, _change_tick),)*)
|
||||
($($name::init_fetch(_world, $name, _last_run, _this_run),)*)
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(
|
||||
|
@ -1451,9 +1203,9 @@ macro_rules! impl_anytuple_fetch {
|
|||
}
|
||||
|
||||
#[allow(clippy::unused_unit)]
|
||||
unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self::Fetch<'w> {
|
||||
unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_run: Tick, _this_run: Tick) -> Self::Fetch<'w> {
|
||||
let ($($name,)*) = state;
|
||||
($(($name::init_fetch(_world, $name, _last_change_tick, _change_tick), false),)*)
|
||||
($(($name::init_fetch(_world, $name, _last_run, _this_run), false),)*)
|
||||
}
|
||||
|
||||
unsafe fn clone_fetch<'w>(
|
||||
|
@ -1589,13 +1341,7 @@ unsafe impl<Q: WorldQuery> WorldQuery for NopWorldQuery<Q> {
|
|||
const IS_ARCHETYPAL: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
unsafe fn init_fetch(
|
||||
_world: &World,
|
||||
_state: &Q::State,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32,
|
||||
) {
|
||||
}
|
||||
unsafe fn init_fetch(_world: &World, _state: &Q::State, _last_run: Tick, _this_run: Tick) {}
|
||||
|
||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
||||
|
||||
|
@ -1642,3 +1388,65 @@ unsafe impl<Q: WorldQuery> WorldQuery for NopWorldQuery<Q> {
|
|||
|
||||
/// SAFETY: `NopFetch` never accesses any data
|
||||
unsafe impl<Q: WorldQuery> ReadOnlyWorldQuery for NopWorldQuery<Q> {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{self as bevy_ecs, system::Query};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct A;
|
||||
|
||||
// Ensures that each field of a `WorldQuery` struct's read-only variant
|
||||
// has the same visibility as its corresponding mutable field.
|
||||
#[test]
|
||||
fn read_only_field_visibility() {
|
||||
mod private {
|
||||
use super::*;
|
||||
|
||||
#[derive(WorldQuery)]
|
||||
#[world_query(mutable)]
|
||||
pub struct Q {
|
||||
pub a: &'static mut A,
|
||||
}
|
||||
}
|
||||
|
||||
let _ = private::QReadOnly { a: &A };
|
||||
|
||||
fn my_system(query: Query<private::Q>) {
|
||||
for q in &query {
|
||||
let _ = &q.a;
|
||||
}
|
||||
}
|
||||
|
||||
crate::system::assert_is_system(my_system);
|
||||
}
|
||||
|
||||
// Ensures that metadata types generated by the WorldQuery macro
|
||||
// do not conflict with user-defined types.
|
||||
// Regression test for https://github.com/bevyengine/bevy/issues/8010.
|
||||
#[test]
|
||||
fn world_query_metadata_collision() {
|
||||
// The metadata types generated would be named `ClientState` and `ClientFetch`,
|
||||
// but they should rename themselves to avoid conflicts.
|
||||
#[derive(WorldQuery)]
|
||||
pub struct Client<S: ClientState> {
|
||||
pub state: &'static S,
|
||||
pub fetch: &'static ClientFetch,
|
||||
}
|
||||
|
||||
pub trait ClientState: Component {}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct ClientFetch;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct C;
|
||||
|
||||
impl ClientState for C {}
|
||||
|
||||
fn client_system(_: Query<Client<C>>) {}
|
||||
|
||||
crate::system::assert_is_system(client_system);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,13 +50,7 @@ unsafe impl<T: Component> WorldQuery for With<T> {
|
|||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {}
|
||||
|
||||
unsafe fn init_fetch(
|
||||
_world: &World,
|
||||
_state: &ComponentId,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32,
|
||||
) {
|
||||
}
|
||||
unsafe fn init_fetch(_world: &World, _state: &ComponentId, _last_run: Tick, _this_run: Tick) {}
|
||||
|
||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
||||
|
||||
|
@ -152,13 +146,7 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
|
|||
|
||||
fn shrink<'wlong: 'wshort, 'wshort>(_: Self::Item<'wlong>) -> Self::Item<'wshort> {}
|
||||
|
||||
unsafe fn init_fetch(
|
||||
_world: &World,
|
||||
_state: &ComponentId,
|
||||
_last_change_tick: u32,
|
||||
_change_tick: u32,
|
||||
) {
|
||||
}
|
||||
unsafe fn init_fetch(_world: &World, _state: &ComponentId, _last_run: Tick, _this_run: Tick) {}
|
||||
|
||||
unsafe fn clone_fetch<'w>(_fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {}
|
||||
|
||||
|
@ -277,10 +265,10 @@ macro_rules! impl_query_filter_tuple {
|
|||
|
||||
const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*;
|
||||
|
||||
unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> {
|
||||
unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> {
|
||||
let ($($filter,)*) = state;
|
||||
($(OrFetch {
|
||||
fetch: $filter::init_fetch(world, $filter, last_change_tick, change_tick),
|
||||
fetch: $filter::init_fetch(world, $filter, last_run, this_run),
|
||||
matches: false,
|
||||
},)*)
|
||||
}
|
||||
|
@ -417,8 +405,8 @@ macro_rules! impl_tick_filter {
|
|||
table_ticks: Option< ThinSlicePtr<'w, UnsafeCell<Tick>>>,
|
||||
marker: PhantomData<T>,
|
||||
sparse_set: Option<&'w ComponentSparseSet>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
}
|
||||
|
||||
// SAFETY: `Self::ReadOnly` is the same as `Self`
|
||||
|
@ -432,7 +420,7 @@ macro_rules! impl_tick_filter {
|
|||
item
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_change_tick: u32, change_tick: u32) -> Self::Fetch<'w> {
|
||||
unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> {
|
||||
Self::Fetch::<'w> {
|
||||
table_ticks: None,
|
||||
sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet)
|
||||
|
@ -443,8 +431,8 @@ macro_rules! impl_tick_filter {
|
|||
.debug_checked_unwrap()
|
||||
}),
|
||||
marker: PhantomData,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -454,8 +442,8 @@ macro_rules! impl_tick_filter {
|
|||
$fetch_name {
|
||||
table_ticks: fetch.table_ticks,
|
||||
sparse_set: fetch.sparse_set,
|
||||
last_change_tick: fetch.last_change_tick,
|
||||
change_tick: fetch.change_tick,
|
||||
last_run: fetch.last_run,
|
||||
this_run: fetch.this_run,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
@ -509,7 +497,7 @@ macro_rules! impl_tick_filter {
|
|||
.debug_checked_unwrap()
|
||||
.get(table_row.index())
|
||||
.deref()
|
||||
.is_newer_than(fetch.last_change_tick, fetch.change_tick)
|
||||
.is_newer_than(fetch.last_run, fetch.this_run)
|
||||
}
|
||||
StorageType::SparseSet => {
|
||||
let sparse_set = &fetch
|
||||
|
@ -518,7 +506,7 @@ macro_rules! impl_tick_filter {
|
|||
$get_sparse_set(sparse_set, entity)
|
||||
.debug_checked_unwrap()
|
||||
.deref()
|
||||
.is_newer_than(fetch.last_change_tick, fetch.change_tick)
|
||||
.is_newer_than(fetch.last_run, fetch.this_run)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -608,7 +596,7 @@ impl_tick_filter!(
|
|||
/// Bevy does not compare components to their previous values.
|
||||
///
|
||||
/// To retain all results without filtering but still check whether they were changed after the
|
||||
/// system last ran, use [`ChangeTrackers<T>`](crate::query::ChangeTrackers).
|
||||
/// system last ran, use [`Ref<T>`](crate::change_detection::Ref).
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{
|
||||
archetype::{ArchetypeEntity, ArchetypeId, Archetypes},
|
||||
component::Tick,
|
||||
entity::{Entities, Entity},
|
||||
prelude::World,
|
||||
query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery},
|
||||
|
@ -29,14 +30,14 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> {
|
|||
pub(crate) unsafe fn new(
|
||||
world: &'w World,
|
||||
query_state: &'s QueryState<Q, F>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
QueryIter {
|
||||
query_state,
|
||||
tables: &world.storages().tables,
|
||||
archetypes: &world.archetypes,
|
||||
cursor: QueryIterationCursor::init(world, query_state, last_change_tick, change_tick),
|
||||
cursor: QueryIterationCursor::init(world, query_state, last_run, this_run),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,21 +99,11 @@ where
|
|||
world: &'w World,
|
||||
query_state: &'s QueryState<Q, F>,
|
||||
entity_list: EntityList,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> QueryManyIter<'w, 's, Q, F, I> {
|
||||
let fetch = Q::init_fetch(
|
||||
world,
|
||||
&query_state.fetch_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let filter = F::init_fetch(
|
||||
world,
|
||||
&query_state.filter_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let fetch = Q::init_fetch(world, &query_state.fetch_state, last_run, this_run);
|
||||
let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run);
|
||||
QueryManyIter {
|
||||
query_state,
|
||||
entities: &world.entities,
|
||||
|
@ -298,8 +289,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize>
|
|||
pub(crate) unsafe fn new(
|
||||
world: &'w World,
|
||||
query_state: &'s QueryState<Q, F>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
// Initialize array with cursors.
|
||||
// There is no FromIterator on arrays, so instead initialize it manually with MaybeUninit
|
||||
|
@ -312,16 +303,16 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize>
|
|||
ptr.write(QueryIterationCursor::init(
|
||||
world,
|
||||
query_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
));
|
||||
}
|
||||
for slot in (1..K).map(|offset| ptr.add(offset)) {
|
||||
slot.write(QueryIterationCursor::init_empty(
|
||||
world,
|
||||
query_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -496,34 +487,24 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
|
|||
unsafe fn init_empty(
|
||||
world: &'w World,
|
||||
query_state: &'s QueryState<Q, F>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
QueryIterationCursor {
|
||||
table_id_iter: [].iter(),
|
||||
archetype_id_iter: [].iter(),
|
||||
..Self::init(world, query_state, last_change_tick, change_tick)
|
||||
..Self::init(world, query_state, last_run, this_run)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init(
|
||||
world: &'w World,
|
||||
query_state: &'s QueryState<Q, F>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self {
|
||||
let fetch = Q::init_fetch(
|
||||
world,
|
||||
&query_state.fetch_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let filter = F::init_fetch(
|
||||
world,
|
||||
&query_state.filter_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let fetch = Q::init_fetch(world, &query_state.fetch_state, last_run, this_run);
|
||||
let filter = F::init_fetch(world, &query_state.filter_state, last_run, this_run);
|
||||
QueryIterationCursor {
|
||||
fetch,
|
||||
filter,
|
||||
|
|
|
@ -61,8 +61,9 @@ impl<T> DebugCheckedUnwrap for Option<T> {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{ReadOnlyWorldQuery, WorldQuery};
|
||||
use crate::prelude::{AnyOf, Entity, Or, QueryState, With, Without};
|
||||
use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without};
|
||||
use crate::query::{ArchetypeFilter, QueryCombinationIter};
|
||||
use crate::schedule::{IntoSystemConfigs, Schedule};
|
||||
use crate::system::{IntoSystem, Query, System, SystemState};
|
||||
use crate::{self as bevy_ecs, component::Component, world::World};
|
||||
use std::any::type_name;
|
||||
|
@ -749,4 +750,33 @@ mod tests {
|
|||
let _: [&Foo; 1] = q.many([e]);
|
||||
let _: &Foo = q.single();
|
||||
}
|
||||
|
||||
// regression test for https://github.com/bevyengine/bevy/pull/8029
|
||||
#[test]
|
||||
fn par_iter_mut_change_detection() {
|
||||
let mut world = World::new();
|
||||
world.spawn((A(1), B(1)));
|
||||
|
||||
fn propagate_system(mut query: Query<(&A, &mut B), Changed<A>>) {
|
||||
query.par_iter_mut().for_each_mut(|(a, mut b)| {
|
||||
b.0 = a.0;
|
||||
});
|
||||
}
|
||||
|
||||
fn modify_system(mut query: Query<&mut A>) {
|
||||
for mut a in &mut query {
|
||||
a.0 = 2;
|
||||
}
|
||||
}
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_systems((propagate_system, modify_system).chain());
|
||||
schedule.run(&mut world);
|
||||
world.clear_trackers();
|
||||
schedule.run(&mut world);
|
||||
world.clear_trackers();
|
||||
|
||||
let values = world.query::<&B>().iter(&world).collect::<Vec<&B>>();
|
||||
assert_eq!(values, vec![&B(2)]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::world::World;
|
||||
use crate::{component::Tick, world::World};
|
||||
use bevy_tasks::ComputeTaskPool;
|
||||
use std::ops::Range;
|
||||
|
||||
|
@ -81,6 +81,8 @@ impl BatchingStrategy {
|
|||
pub struct QueryParIter<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> {
|
||||
pub(crate) world: &'w World,
|
||||
pub(crate) state: &'s QueryState<Q, F>,
|
||||
pub(crate) last_run: Tick,
|
||||
pub(crate) this_run: Tick,
|
||||
pub(crate) batching_strategy: BatchingStrategy,
|
||||
}
|
||||
|
||||
|
@ -148,12 +150,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
|||
) {
|
||||
let thread_count = ComputeTaskPool::get().thread_num();
|
||||
if thread_count <= 1 {
|
||||
self.state.for_each_unchecked_manual(
|
||||
self.world,
|
||||
func,
|
||||
self.world.last_change_tick(),
|
||||
self.world.read_change_tick(),
|
||||
);
|
||||
self.state
|
||||
.for_each_unchecked_manual(self.world, func, self.last_run, self.this_run);
|
||||
} else {
|
||||
// Need a batch size of at least 1.
|
||||
let batch_size = self.get_batch_size(thread_count).max(1);
|
||||
|
@ -161,8 +159,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryParIter<'w, 's, Q, F> {
|
|||
self.world,
|
||||
batch_size,
|
||||
func,
|
||||
self.world.last_change_tick(),
|
||||
self.world.read_change_tick(),
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId},
|
||||
component::ComponentId,
|
||||
component::{ComponentId, Tick},
|
||||
entity::Entity,
|
||||
prelude::FromWorld,
|
||||
query::{
|
||||
|
@ -130,11 +130,11 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
|
||||
/// Checks if the query is empty for the given [`World`], where the last change and current tick are given.
|
||||
#[inline]
|
||||
pub fn is_empty(&self, world: &World, last_change_tick: u32, change_tick: u32) -> bool {
|
||||
pub fn is_empty(&self, world: &World, last_run: Tick, this_run: Tick) -> bool {
|
||||
// SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access
|
||||
unsafe {
|
||||
self.as_nop()
|
||||
.iter_unchecked_manual(world, last_change_tick, change_tick)
|
||||
.iter_unchecked_manual(world, last_run, this_run)
|
||||
.next()
|
||||
.is_none()
|
||||
}
|
||||
|
@ -390,8 +390,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
&self,
|
||||
world: &'w World,
|
||||
entity: Entity,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Result<Q::Item<'w>, QueryEntityError> {
|
||||
let location = world
|
||||
.entities
|
||||
|
@ -407,8 +407,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
.archetypes
|
||||
.get(location.archetype_id)
|
||||
.debug_checked_unwrap();
|
||||
let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick);
|
||||
let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick);
|
||||
let mut fetch = Q::init_fetch(world, &self.fetch_state, last_run, this_run);
|
||||
let mut filter = F::init_fetch(world, &self.filter_state, last_run, this_run);
|
||||
|
||||
let table = world
|
||||
.storages()
|
||||
|
@ -436,20 +436,17 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
&self,
|
||||
world: &'w World,
|
||||
entities: [Entity; N],
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> {
|
||||
let mut values = [(); N].map(|_| MaybeUninit::uninit());
|
||||
|
||||
for (value, entity) in std::iter::zip(&mut values, entities) {
|
||||
// SAFETY: fetch is read-only
|
||||
// and world must be validated
|
||||
let item = self.as_readonly().get_unchecked_manual(
|
||||
world,
|
||||
entity,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
)?;
|
||||
let item = self
|
||||
.as_readonly()
|
||||
.get_unchecked_manual(world, entity, last_run, this_run)?;
|
||||
*value = MaybeUninit::new(item);
|
||||
}
|
||||
|
||||
|
@ -471,8 +468,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
&self,
|
||||
world: &'w World,
|
||||
entities: [Entity; N],
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Result<[Q::Item<'w>; N], QueryEntityError> {
|
||||
// Verify that all entities are unique
|
||||
for i in 0..N {
|
||||
|
@ -486,7 +483,7 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
let mut values = [(); N].map(|_| MaybeUninit::uninit());
|
||||
|
||||
for (value, entity) in std::iter::zip(&mut values, entities) {
|
||||
let item = self.get_unchecked_manual(world, entity, last_change_tick, change_tick)?;
|
||||
let item = self.get_unchecked_manual(world, entity, last_run, this_run)?;
|
||||
*value = MaybeUninit::new(item);
|
||||
}
|
||||
|
||||
|
@ -708,10 +705,10 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
pub(crate) unsafe fn iter_unchecked_manual<'w, 's>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> QueryIter<'w, 's, Q, F> {
|
||||
QueryIter::new(world, self, last_change_tick, change_tick)
|
||||
QueryIter::new(world, self, last_run, this_run)
|
||||
}
|
||||
|
||||
/// Returns an [`Iterator`] for the given [`World`] and list of [`Entity`]'s, where the last change and
|
||||
|
@ -729,13 +726,13 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
&'s self,
|
||||
entities: EntityList,
|
||||
world: &'w World,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> QueryManyIter<'w, 's, Q, F, EntityList::IntoIter>
|
||||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
QueryManyIter::new(world, self, entities, last_change_tick, change_tick)
|
||||
QueryManyIter::new(world, self, entities, last_run, this_run)
|
||||
}
|
||||
|
||||
/// Returns an [`Iterator`] over all possible combinations of `K` query results for the
|
||||
|
@ -752,10 +749,10 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
pub(crate) unsafe fn iter_combinations_unchecked_manual<'w, 's, const K: usize>(
|
||||
&'s self,
|
||||
world: &'w World,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> QueryCombinationIter<'w, 's, Q, F, K> {
|
||||
QueryCombinationIter::new(world, self, last_change_tick, change_tick)
|
||||
QueryCombinationIter::new(world, self, last_run, this_run)
|
||||
}
|
||||
|
||||
/// Runs `func` on each query result for the given [`World`]. This is faster than the equivalent
|
||||
|
@ -823,6 +820,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
QueryParIter {
|
||||
world,
|
||||
state: self,
|
||||
last_run: world.last_change_tick(),
|
||||
this_run: world.read_change_tick(),
|
||||
batching_strategy: BatchingStrategy::new(),
|
||||
}
|
||||
}
|
||||
|
@ -835,9 +834,12 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
#[inline]
|
||||
pub fn par_iter_mut<'w, 's>(&'s mut self, world: &'w mut World) -> QueryParIter<'w, 's, Q, F> {
|
||||
self.update_archetypes(world);
|
||||
let this_run = world.change_tick();
|
||||
QueryParIter {
|
||||
world,
|
||||
state: self,
|
||||
last_run: world.last_change_tick(),
|
||||
this_run,
|
||||
batching_strategy: BatchingStrategy::new(),
|
||||
}
|
||||
}
|
||||
|
@ -856,13 +858,13 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
&self,
|
||||
world: &'w World,
|
||||
mut func: FN,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) {
|
||||
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
|
||||
// QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
|
||||
let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick);
|
||||
let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick);
|
||||
let mut fetch = Q::init_fetch(world, &self.fetch_state, last_run, this_run);
|
||||
let mut filter = F::init_fetch(world, &self.filter_state, last_run, this_run);
|
||||
|
||||
let tables = &world.storages().tables;
|
||||
if Q::IS_DENSE && F::IS_DENSE {
|
||||
|
@ -931,8 +933,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
world: &'w World,
|
||||
batch_size: usize,
|
||||
func: FN,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) {
|
||||
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
|
||||
// QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual
|
||||
|
@ -950,18 +952,10 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
let func = func.clone();
|
||||
let len = batch_size.min(table.entity_count() - offset);
|
||||
let task = async move {
|
||||
let mut fetch = Q::init_fetch(
|
||||
world,
|
||||
&self.fetch_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let mut filter = F::init_fetch(
|
||||
world,
|
||||
&self.filter_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let mut fetch =
|
||||
Q::init_fetch(world, &self.fetch_state, last_run, this_run);
|
||||
let mut filter =
|
||||
F::init_fetch(world, &self.filter_state, last_run, this_run);
|
||||
let tables = &world.storages().tables;
|
||||
let table = tables.get(*table_id).debug_checked_unwrap();
|
||||
let entities = table.entities();
|
||||
|
@ -1002,18 +996,10 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
let func = func.clone();
|
||||
let len = batch_size.min(archetype.len() - offset);
|
||||
let task = async move {
|
||||
let mut fetch = Q::init_fetch(
|
||||
world,
|
||||
&self.fetch_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let mut filter = F::init_fetch(
|
||||
world,
|
||||
&self.filter_state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
);
|
||||
let mut fetch =
|
||||
Q::init_fetch(world, &self.fetch_state, last_run, this_run);
|
||||
let mut filter =
|
||||
F::init_fetch(world, &self.filter_state, last_run, this_run);
|
||||
let tables = &world.storages().tables;
|
||||
let archetype =
|
||||
world.archetypes.get(*archetype_id).debug_checked_unwrap();
|
||||
|
@ -1161,10 +1147,10 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
|
|||
pub unsafe fn get_single_unchecked_manual<'w>(
|
||||
&self,
|
||||
world: &'w World,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Result<Q::Item<'w>, QuerySingleError> {
|
||||
let mut query = self.iter_unchecked_manual(world, last_change_tick, change_tick);
|
||||
let mut query = self.iter_unchecked_manual(world, last_run, this_run);
|
||||
let first = query.next();
|
||||
let extra = query.next().is_some();
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ pub struct ReflectComponent(ReflectComponentFns);
|
|||
/// world.
|
||||
#[derive(Clone)]
|
||||
pub struct ReflectComponentFns {
|
||||
/// Function pointer implementing [`ReflectComponent::from_world()`].
|
||||
pub from_world: fn(&mut World) -> Box<dyn Reflect>,
|
||||
/// Function pointer implementing [`ReflectComponent::insert()`].
|
||||
pub insert: fn(&mut EntityMut, &dyn Reflect),
|
||||
/// Function pointer implementing [`ReflectComponent::apply()`].
|
||||
|
@ -79,6 +81,11 @@ impl ReflectComponentFns {
|
|||
}
|
||||
|
||||
impl ReflectComponent {
|
||||
/// Constructs default reflected [`Component`] from world using [`from_world()`](FromWorld::from_world).
|
||||
pub fn from_world(&self, world: &mut World) -> Box<dyn Reflect> {
|
||||
(self.0.from_world)(world)
|
||||
}
|
||||
|
||||
/// Insert a reflected [`Component`] into the entity like [`insert()`](crate::world::EntityMut::insert).
|
||||
pub fn insert(&self, entity: &mut EntityMut, component: &dyn Reflect) {
|
||||
(self.0.insert)(entity, component);
|
||||
|
@ -170,6 +177,7 @@ impl ReflectComponent {
|
|||
impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {
|
||||
fn from_type() -> Self {
|
||||
ReflectComponent(ReflectComponentFns {
|
||||
from_world: |world| Box::new(C::from_world(world)),
|
||||
insert: |entity, reflected_component| {
|
||||
let mut component = entity.world_scope(|world| C::from_world(world));
|
||||
component.apply(reflected_component);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
component::{Component, ComponentId, ComponentIdFor},
|
||||
component::{Component, ComponentId, ComponentIdFor, Tick},
|
||||
entity::Entity,
|
||||
event::{EventId, Events, ManualEventIterator, ManualEventIteratorWithId, ManualEventReader},
|
||||
prelude::Local,
|
||||
|
@ -265,7 +265,7 @@ unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
|
|||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.removed_components()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
use std::any::TypeId;
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Not;
|
||||
|
||||
use crate::component::{self, ComponentId};
|
||||
use crate::query::Access;
|
||||
use crate::system::{CombinatorSystem, Combine, IntoSystem, ReadOnlySystem, System};
|
||||
use crate::world::World;
|
||||
|
||||
pub type BoxedCondition = Box<dyn ReadOnlySystem<In = (), Out = bool>>;
|
||||
|
||||
|
@ -26,7 +31,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
|
|||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # fn my_system() {}
|
||||
/// app.add_system(
|
||||
/// app.add_systems(
|
||||
/// // The `resource_equals` run condition will panic since we don't initialize `R`,
|
||||
/// // just like if we used `Res<R>` in a system.
|
||||
/// my_system.run_if(resource_equals(R(0))),
|
||||
|
@ -43,7 +48,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
|
|||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # fn my_system() {}
|
||||
/// app.add_system(
|
||||
/// app.add_systems(
|
||||
/// // `resource_equals` will only get run if the resource `R` exists.
|
||||
/// my_system.run_if(resource_exists::<R>().and_then(resource_equals(R(0)))),
|
||||
/// );
|
||||
|
@ -81,7 +86,7 @@ pub trait Condition<Marker>: sealed::Condition<Marker> {
|
|||
/// # let mut world = World::new();
|
||||
/// # #[derive(Resource)] struct C(bool);
|
||||
/// # fn my_system(mut c: ResMut<C>) { c.0 = true; }
|
||||
/// app.add_system(
|
||||
/// app.add_systems(
|
||||
/// // Only run the system if either `A` or `B` exist.
|
||||
/// my_system.run_if(resource_exists::<A>().or_else(resource_exists::<B>())),
|
||||
/// );
|
||||
|
@ -131,18 +136,47 @@ mod sealed {
|
|||
}
|
||||
|
||||
pub mod common_conditions {
|
||||
use super::Condition;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::NotSystem;
|
||||
use crate::{
|
||||
change_detection::DetectChanges,
|
||||
event::{Event, EventReader},
|
||||
prelude::{Component, Query, With},
|
||||
schedule::{State, States},
|
||||
system::{In, IntoPipeSystem, Res, Resource},
|
||||
system::{IntoSystem, Res, Resource, System},
|
||||
};
|
||||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if the first time the condition is run and false every time after
|
||||
pub fn run_once() -> impl FnMut() -> bool {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// // `run_once` will only return true the first time it's evaluated
|
||||
/// my_system.run_if(run_once()),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // This is the first time the condition will be evaluated so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// // This is the seconds time the condition will be evaluated so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn run_once() -> impl FnMut() -> bool + Clone {
|
||||
let mut has_run = false;
|
||||
move || {
|
||||
if !has_run {
|
||||
|
@ -156,7 +190,33 @@ pub mod common_conditions {
|
|||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if the resource exists.
|
||||
pub fn resource_exists<T>() -> impl FnMut(Option<Res<T>>) -> bool
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// app.add_systems(
|
||||
/// // `resource_exsists` will only return true if the given resource exsists in the world
|
||||
/// my_system.run_if(resource_exists::<Counter>()),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `Counter` hasn't been added so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// world.init_resource::<Counter>();
|
||||
///
|
||||
/// // `Counter` has now been added so `my_system` can run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn resource_exists<T>() -> impl FnMut(Option<Res<T>>) -> bool + Clone
|
||||
where
|
||||
T: Resource,
|
||||
{
|
||||
|
@ -169,6 +229,33 @@ pub mod common_conditions {
|
|||
/// # Panics
|
||||
///
|
||||
/// The condition will panic if the resource does not exist.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default, PartialEq)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// // `resource_equals` will only return true if the given resource equals the given value
|
||||
/// my_system.run_if(resource_equals(Counter(0))),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `Counter` is `0` so `my_system` can run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// // `Counter` is no longer `0` so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn resource_equals<T>(value: T) -> impl FnMut(Res<T>) -> bool
|
||||
where
|
||||
T: Resource + PartialEq,
|
||||
|
@ -180,6 +267,37 @@ pub mod common_conditions {
|
|||
/// if the resource exists and is equal to `value`.
|
||||
///
|
||||
/// The condition will return `false` if the resource does not exist.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default, PartialEq)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// app.add_systems(
|
||||
/// // `resource_exists_and_equals` will only return true
|
||||
/// // if the given resource exsists and equals the given value
|
||||
/// my_system.run_if(resource_exists_and_equals(Counter(0))),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `Counter` hasn't been added so `my_system` can't run
|
||||
/// app.run(&mut world);
|
||||
/// world.init_resource::<Counter>();
|
||||
///
|
||||
/// // `Counter` is `0` so `my_system` can run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// // `Counter` is no longer `0` so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn resource_exists_and_equals<T>(value: T) -> impl FnMut(Option<Res<T>>) -> bool
|
||||
where
|
||||
T: Resource + PartialEq,
|
||||
|
@ -192,7 +310,36 @@ pub mod common_conditions {
|
|||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if the resource of the given type has been added since the condition was last checked.
|
||||
pub fn resource_added<T>() -> impl FnMut(Option<Res<T>>) -> bool
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// app.add_systems(
|
||||
/// // `resource_added` will only return true if the
|
||||
/// // given resource was just added
|
||||
/// my_system.run_if(resource_added::<Counter>()),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// world.init_resource::<Counter>();
|
||||
///
|
||||
/// // `Counter` was just added so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// // `Counter` was not just added so `my_system` will not run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn resource_added<T>() -> impl FnMut(Option<Res<T>>) -> bool + Clone
|
||||
where
|
||||
T: Resource,
|
||||
{
|
||||
|
@ -213,7 +360,43 @@ pub mod common_conditions {
|
|||
/// # Panics
|
||||
///
|
||||
/// The condition will panic if the resource does not exist.
|
||||
pub fn resource_changed<T>() -> impl FnMut(Res<T>) -> bool
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// // `resource_changed` will only return true if the
|
||||
/// // given resource was just changed (or added)
|
||||
/// my_system.run_if(
|
||||
/// resource_changed::<Counter>()
|
||||
/// // By default detecting changes will also trigger if the resource was
|
||||
/// // just added, this won't work with my example so I will addd a second
|
||||
/// // condition to make sure the resource wasn't just added
|
||||
/// .and_then(not(resource_added::<Counter>()))
|
||||
/// ),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `Counter` hasn't been changed so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.resource_mut::<Counter>().0 = 50;
|
||||
///
|
||||
/// // `Counter` was just changed so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 51);
|
||||
/// ```
|
||||
pub fn resource_changed<T>() -> impl FnMut(Res<T>) -> bool + Clone
|
||||
where
|
||||
T: Resource,
|
||||
{
|
||||
|
@ -231,7 +414,46 @@ pub mod common_conditions {
|
|||
/// This run condition does not detect when the resource is removed.
|
||||
///
|
||||
/// The condition will return `false` if the resource does not exist.
|
||||
pub fn resource_exists_and_changed<T>() -> impl FnMut(Option<Res<T>>) -> bool
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// app.add_systems(
|
||||
/// // `resource_exists_and_changed` will only return true if the
|
||||
/// // given resource exsists and was just changed (or added)
|
||||
/// my_system.run_if(
|
||||
/// resource_exists_and_changed::<Counter>()
|
||||
/// // By default detecting changes will also trigger if the resource was
|
||||
/// // just added, this won't work with my example so I will addd a second
|
||||
/// // condition to make sure the resource wasn't just added
|
||||
/// .and_then(not(resource_added::<Counter>()))
|
||||
/// ),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `Counter` doesn't exist so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// world.init_resource::<Counter>();
|
||||
///
|
||||
/// // `Counter` hasn't been changed so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.resource_mut::<Counter>().0 = 50;
|
||||
///
|
||||
/// // `Counter` was just changed so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 51);
|
||||
/// ```
|
||||
pub fn resource_exists_and_changed<T>() -> impl FnMut(Option<Res<T>>) -> bool + Clone
|
||||
where
|
||||
T: Resource,
|
||||
{
|
||||
|
@ -253,7 +475,57 @@ pub mod common_conditions {
|
|||
/// has been removed since the run condition was last checked.
|
||||
///
|
||||
/// The condition will return `false` if the resource does not exist.
|
||||
pub fn resource_changed_or_removed<T>() -> impl FnMut(Option<Res<T>>) -> bool
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// // `resource_changed_or_removed` will only return true if the
|
||||
/// // given resource was just changed or removed (or added)
|
||||
/// my_system.run_if(
|
||||
/// resource_changed_or_removed::<Counter>()
|
||||
/// // By default detecting changes will also trigger if the resource was
|
||||
/// // just added, this won't work with my example so I will addd a second
|
||||
/// // condition to make sure the resource wasn't just added
|
||||
/// .and_then(not(resource_added::<Counter>()))
|
||||
/// ),
|
||||
/// );
|
||||
///
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct MyResource;
|
||||
///
|
||||
/// // If `Counter` exists, increment it, otherwise insert `MyResource`
|
||||
/// fn my_system(mut commands: Commands, mut counter: Option<ResMut<Counter>>) {
|
||||
/// if let Some(mut counter) = counter {
|
||||
/// counter.0 += 1;
|
||||
/// } else {
|
||||
/// commands.init_resource::<MyResource>();
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // `Counter` hasn't been changed so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.resource_mut::<Counter>().0 = 50;
|
||||
///
|
||||
/// // `Counter` was just changed so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 51);
|
||||
///
|
||||
/// world.remove_resource::<Counter>();
|
||||
///
|
||||
/// // `Counter` was just removed so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.contains_resource::<MyResource>(), true);
|
||||
/// ```
|
||||
pub fn resource_changed_or_removed<T>() -> impl FnMut(Option<Res<T>>) -> bool + Clone
|
||||
where
|
||||
T: Resource,
|
||||
{
|
||||
|
@ -273,7 +545,42 @@ pub mod common_conditions {
|
|||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if the resource of the given type has been removed since the condition was last checked.
|
||||
pub fn resource_removed<T>() -> impl FnMut(Option<Res<T>>) -> bool
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// // `resource_removed` will only return true if the
|
||||
/// // given resource was just removed
|
||||
/// my_system.run_if(resource_removed::<MyResource>()),
|
||||
/// );
|
||||
///
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct MyResource;
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// world.init_resource::<MyResource>();
|
||||
///
|
||||
/// // `MyResource` hasn't just been removed so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.remove_resource::<MyResource>();
|
||||
///
|
||||
/// // `MyResource` was just removed so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn resource_removed<T>() -> impl FnMut(Option<Res<T>>) -> bool + Clone
|
||||
where
|
||||
T: Resource,
|
||||
{
|
||||
|
@ -293,7 +600,44 @@ pub mod common_conditions {
|
|||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if the state machine exists.
|
||||
pub fn state_exists<S: States>() -> impl FnMut(Option<Res<State<S>>>) -> bool {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
|
||||
/// enum GameState {
|
||||
/// #[default]
|
||||
/// Playing,
|
||||
/// Paused,
|
||||
/// }
|
||||
///
|
||||
/// app.add_systems(
|
||||
/// // `state_exists` will only return true if the
|
||||
/// // given state exsists
|
||||
/// my_system.run_if(state_exists::<GameState>()),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `GameState` does not yet exist `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.init_resource::<State<GameState>>();
|
||||
///
|
||||
/// // `GameState` now exists so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn state_exists<S: States>() -> impl FnMut(Option<Res<State<S>>>) -> bool + Clone {
|
||||
move |current_state: Option<Res<State<S>>>| current_state.is_some()
|
||||
}
|
||||
|
||||
|
@ -303,7 +647,51 @@ pub mod common_conditions {
|
|||
/// # Panics
|
||||
///
|
||||
/// The condition will panic if the resource does not exist.
|
||||
pub fn in_state<S: States>(state: S) -> impl FnMut(Res<State<S>>) -> bool {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
|
||||
/// enum GameState {
|
||||
/// #[default]
|
||||
/// Playing,
|
||||
/// Paused,
|
||||
/// }
|
||||
///
|
||||
/// world.init_resource::<State<GameState>>();
|
||||
///
|
||||
/// app.add_systems((
|
||||
/// // `in_state` will only return true if the
|
||||
/// // given state equals the given value
|
||||
/// play_system.run_if(in_state(GameState::Playing)),
|
||||
/// pause_system.run_if(in_state(GameState::Paused)),
|
||||
/// ));
|
||||
///
|
||||
/// fn play_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// fn pause_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 -= 1;
|
||||
/// }
|
||||
///
|
||||
/// // We default to `GameState::Playing` so `play_system` runs
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// *world.resource_mut::<State<GameState>>() = State(GameState::Paused);
|
||||
///
|
||||
/// // Now that we are in `GameState::Pause`, `pause_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
/// ```
|
||||
pub fn in_state<S: States>(state: S) -> impl FnMut(Res<State<S>>) -> bool + Clone {
|
||||
move |current_state: Res<State<S>>| current_state.0 == state
|
||||
}
|
||||
|
||||
|
@ -311,9 +699,57 @@ pub mod common_conditions {
|
|||
/// if the state machine exists and is currently in `state`.
|
||||
///
|
||||
/// The condition will return `false` if the state does not exist.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
|
||||
/// enum GameState {
|
||||
/// #[default]
|
||||
/// Playing,
|
||||
/// Paused,
|
||||
/// }
|
||||
///
|
||||
/// app.add_systems((
|
||||
/// // `state_exists_and_equals` will only return true if the
|
||||
/// // given state exsists and equals the given value
|
||||
/// play_system.run_if(state_exists_and_equals(GameState::Playing)),
|
||||
/// pause_system.run_if(state_exists_and_equals(GameState::Paused)),
|
||||
/// ));
|
||||
///
|
||||
/// fn play_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// fn pause_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 -= 1;
|
||||
/// }
|
||||
///
|
||||
/// // `GameState` does not yet exists so neither system will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.init_resource::<State<GameState>>();
|
||||
///
|
||||
/// // We default to `GameState::Playing` so `play_system` runs
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// *world.resource_mut::<State<GameState>>() = State(GameState::Paused);
|
||||
///
|
||||
/// // Now that we are in `GameState::Pause`, `pause_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
/// ```
|
||||
pub fn state_exists_and_equals<S: States>(
|
||||
state: S,
|
||||
) -> impl FnMut(Option<Res<State<S>>>) -> bool {
|
||||
) -> impl FnMut(Option<Res<State<S>>>) -> bool + Clone {
|
||||
move |current_state: Option<Res<State<S>>>| match current_state {
|
||||
Some(current_state) => current_state.0 == state,
|
||||
None => false,
|
||||
|
@ -329,13 +765,90 @@ pub mod common_conditions {
|
|||
/// # Panics
|
||||
///
|
||||
/// The condition will panic if the resource does not exist.
|
||||
pub fn state_changed<S: States>() -> impl FnMut(Res<State<S>>) -> bool {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// #[derive(States, Clone, Copy, Default, Eq, PartialEq, Hash, Debug)]
|
||||
/// enum GameState {
|
||||
/// #[default]
|
||||
/// Playing,
|
||||
/// Paused,
|
||||
/// }
|
||||
///
|
||||
/// world.init_resource::<State<GameState>>();
|
||||
///
|
||||
/// app.add_systems(
|
||||
/// // `state_changed` will only return true if the
|
||||
/// // given states value has just been updated or
|
||||
/// // the state has just been added
|
||||
/// my_system.run_if(state_changed::<GameState>()),
|
||||
/// );
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // `GameState` has just been added so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// // `GameState` has not been updated so `my_system` will not run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
///
|
||||
/// *world.resource_mut::<State<GameState>>() = State(GameState::Paused);
|
||||
///
|
||||
/// // Now that `GameState` has been updated `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 2);
|
||||
/// ```
|
||||
pub fn state_changed<S: States>() -> impl FnMut(Res<State<S>>) -> bool + Clone {
|
||||
move |current_state: Res<State<S>>| current_state.is_changed()
|
||||
}
|
||||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if there are any new events of the given type since it was last called.
|
||||
pub fn on_event<T: Event>() -> impl FnMut(EventReader<T>) -> bool {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// # world.init_resource::<Events<MyEvent>>();
|
||||
/// # app.add_systems(Events::<MyEvent>::update_system.before(my_system));
|
||||
///
|
||||
/// app.add_systems(
|
||||
/// my_system.run_if(on_event::<MyEvent>()),
|
||||
/// );
|
||||
///
|
||||
/// struct MyEvent;
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // No new `MyEvent` events have been push so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.resource_mut::<Events<MyEvent>>().send(MyEvent);
|
||||
///
|
||||
/// // A `MyEvent` event has been push so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn on_event<T: Event>() -> impl FnMut(EventReader<T>) -> bool + Clone {
|
||||
// The events need to be consumed, so that there are no false positives on subsequent
|
||||
// calls of the run condition. Simply checking `is_empty` would not be enough.
|
||||
// PERF: note that `count` is efficient (not actually looping/iterating),
|
||||
|
@ -345,39 +858,169 @@ pub mod common_conditions {
|
|||
|
||||
/// Generates a [`Condition`](super::Condition)-satisfying closure that returns `true`
|
||||
/// if there are any entities with the given component type.
|
||||
pub fn any_with_component<T: Component>() -> impl FnMut(Query<(), With<T>>) -> bool {
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// my_system.run_if(any_with_component::<MyComponent>()),
|
||||
/// );
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// struct MyComponent;
|
||||
///
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
///
|
||||
/// // No entities exist yet with a `MyComponent` component so `my_system` won't run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
///
|
||||
/// world.spawn(MyComponent);
|
||||
///
|
||||
/// // An entities with `MyComponent` now exists so `my_system` will run
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 1);
|
||||
/// ```
|
||||
pub fn any_with_component<T: Component>() -> impl FnMut(Query<(), With<T>>) -> bool + Clone {
|
||||
move |query: Query<(), With<T>>| !query.is_empty()
|
||||
}
|
||||
|
||||
/// Generates a [`Condition`](super::Condition) that inverses the result of passed one.
|
||||
///
|
||||
/// # Examples
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// // Building a new schedule/app...
|
||||
/// let mut sched = Schedule::default();
|
||||
/// sched.add_system(
|
||||
/// // This system will never run.
|
||||
/// my_system.run_if(not(always_true))
|
||||
/// )
|
||||
/// // ...
|
||||
/// # ;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Resource, Default)]
|
||||
/// # struct Counter(u8);
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # let mut world = World::new();
|
||||
/// # sched.run(&mut world);
|
||||
/// # world.init_resource::<Counter>();
|
||||
/// app.add_systems(
|
||||
/// // `not` will inverse any condition you pass in.
|
||||
/// // Since the condition we choose always returns true
|
||||
/// // this system will never run
|
||||
/// my_system.run_if(not(always)),
|
||||
/// );
|
||||
///
|
||||
/// // A condition that always returns true.
|
||||
/// fn always_true() -> bool {
|
||||
/// true
|
||||
/// fn my_system(mut counter: ResMut<Counter>) {
|
||||
/// counter.0 += 1;
|
||||
/// }
|
||||
/// #
|
||||
/// # fn my_system() { unreachable!() }
|
||||
///
|
||||
/// fn always() -> bool {
|
||||
/// true
|
||||
/// }
|
||||
///
|
||||
/// app.run(&mut world);
|
||||
/// assert_eq!(world.resource::<Counter>().0, 0);
|
||||
/// ```
|
||||
pub fn not<Marker>(condition: impl Condition<Marker>) -> impl Condition<()> {
|
||||
condition.pipe(|In(val): In<bool>| !val)
|
||||
pub fn not<Marker, TOut, T>(condition: T) -> NotSystem<T::System>
|
||||
where
|
||||
TOut: std::ops::Not,
|
||||
T: IntoSystem<(), TOut, Marker>,
|
||||
{
|
||||
let condition = IntoSystem::into_system(condition);
|
||||
let name = format!("!{}", condition.name());
|
||||
NotSystem::<T::System> {
|
||||
condition,
|
||||
name: Cow::Owned(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Invokes [`Not`] with the output of another system.
|
||||
///
|
||||
/// See [`common_conditions::not`] for examples.
|
||||
#[derive(Clone)]
|
||||
pub struct NotSystem<T: System>
|
||||
where
|
||||
T::Out: Not,
|
||||
{
|
||||
condition: T,
|
||||
name: Cow<'static, str>,
|
||||
}
|
||||
impl<T: System> System for NotSystem<T>
|
||||
where
|
||||
T::Out: Not,
|
||||
{
|
||||
type In = T::In;
|
||||
type Out = <T::Out as Not>::Output;
|
||||
|
||||
fn name(&self) -> Cow<'static, str> {
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn type_id(&self) -> TypeId {
|
||||
TypeId::of::<T>()
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.condition.component_access()
|
||||
}
|
||||
|
||||
fn archetype_component_access(&self) -> &Access<crate::archetype::ArchetypeComponentId> {
|
||||
self.condition.archetype_component_access()
|
||||
}
|
||||
|
||||
fn is_send(&self) -> bool {
|
||||
self.condition.is_send()
|
||||
}
|
||||
|
||||
fn is_exclusive(&self) -> bool {
|
||||
self.condition.is_exclusive()
|
||||
}
|
||||
|
||||
unsafe fn run_unsafe(&mut self, input: Self::In, world: &World) -> Self::Out {
|
||||
// SAFETY: The inner condition system asserts its own safety.
|
||||
!self.condition.run_unsafe(input, world)
|
||||
}
|
||||
|
||||
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
|
||||
!self.condition.run(input, world)
|
||||
}
|
||||
|
||||
fn apply_buffers(&mut self, world: &mut World) {
|
||||
self.condition.apply_buffers(world);
|
||||
}
|
||||
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.condition.initialize(world);
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(&mut self, world: &World) {
|
||||
self.condition.update_archetype_component_access(world);
|
||||
}
|
||||
|
||||
fn check_change_tick(&mut self, change_tick: component::Tick) {
|
||||
self.condition.check_change_tick(change_tick);
|
||||
}
|
||||
|
||||
fn get_last_run(&self) -> component::Tick {
|
||||
self.condition.get_last_run()
|
||||
}
|
||||
|
||||
fn set_last_run(&mut self, last_run: component::Tick) {
|
||||
self.condition.set_last_run(last_run);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: This trait is only implemented when the inner system is read-only.
|
||||
// The `Not` condition does not modify access, and thus cannot transform a read-only system into one that is not.
|
||||
unsafe impl<T> ReadOnlySystem for NotSystem<T>
|
||||
where
|
||||
T: ReadOnlySystem,
|
||||
T::Out: Not,
|
||||
{
|
||||
}
|
||||
|
||||
/// Combines the outputs of two systems using the `&&` operator.
|
||||
pub type AndThen<A, B> = CombinatorSystem<AndThenMarker, A, B>;
|
||||
|
||||
|
@ -425,3 +1068,142 @@ where
|
|||
a(input) || b(input)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{common_conditions::*, Condition};
|
||||
use crate as bevy_ecs;
|
||||
use crate::component::Component;
|
||||
use crate::schedule::IntoSystemConfigs;
|
||||
use crate::schedule::{common_conditions::not, State, States};
|
||||
use crate::system::Local;
|
||||
use crate::{change_detection::ResMut, schedule::Schedule, world::World};
|
||||
use bevy_ecs_macros::Resource;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct Counter(usize);
|
||||
|
||||
fn increment_counter(mut counter: ResMut<Counter>) {
|
||||
counter.0 += 1;
|
||||
}
|
||||
|
||||
fn every_other_time(mut has_ran: Local<bool>) -> bool {
|
||||
*has_ran = !*has_ran;
|
||||
*has_ran
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_condition() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Counter>();
|
||||
let mut schedule = Schedule::new();
|
||||
|
||||
// Run every other cycle
|
||||
schedule.add_systems(increment_counter.run_if(every_other_time));
|
||||
|
||||
schedule.run(&mut world);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 1);
|
||||
schedule.run(&mut world);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 2);
|
||||
|
||||
// Run every other cycle oppsite to the last one
|
||||
schedule.add_systems(increment_counter.run_if(not(every_other_time)));
|
||||
|
||||
schedule.run(&mut world);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 4);
|
||||
schedule.run(&mut world);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_condition_combinators() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Counter>();
|
||||
let mut schedule = Schedule::new();
|
||||
|
||||
// Always run
|
||||
schedule.add_systems(increment_counter.run_if(every_other_time.or_else(|| true)));
|
||||
// Run every other cycle
|
||||
schedule.add_systems(increment_counter.run_if(every_other_time.and_then(|| true)));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 2);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_run_conditions() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Counter>();
|
||||
let mut schedule = Schedule::new();
|
||||
|
||||
// Run every other cycle
|
||||
schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| true));
|
||||
// Never run
|
||||
schedule.add_systems(increment_counter.run_if(every_other_time).run_if(|| false));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 1);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_run_conditions_is_and_operation() {
|
||||
let mut world = World::new();
|
||||
world.init_resource::<Counter>();
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
|
||||
// This should never run, if multiple run conditions worked
|
||||
// like an OR condition then it would always run
|
||||
schedule.add_systems(
|
||||
increment_counter
|
||||
.run_if(every_other_time)
|
||||
.run_if(not(every_other_time)),
|
||||
);
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 0);
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0, 0);
|
||||
}
|
||||
|
||||
#[derive(States, PartialEq, Eq, Debug, Default, Hash, Clone)]
|
||||
enum TestState {
|
||||
#[default]
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct TestComponent;
|
||||
|
||||
fn test_system() {}
|
||||
|
||||
// Ensure distributive_run_if compiles with the common conditions.
|
||||
#[test]
|
||||
fn distributive_run_if_compiles() {
|
||||
Schedule::default().add_systems(
|
||||
(test_system, test_system)
|
||||
.distributive_run_if(run_once())
|
||||
.distributive_run_if(resource_exists::<State<TestState>>())
|
||||
.distributive_run_if(resource_added::<State<TestState>>())
|
||||
.distributive_run_if(resource_changed::<State<TestState>>())
|
||||
.distributive_run_if(resource_exists_and_changed::<State<TestState>>())
|
||||
.distributive_run_if(resource_changed_or_removed::<State<TestState>>())
|
||||
.distributive_run_if(resource_removed::<State<TestState>>())
|
||||
.distributive_run_if(state_exists::<TestState>())
|
||||
.distributive_run_if(in_state(TestState::A))
|
||||
.distributive_run_if(state_changed::<TestState>())
|
||||
.distributive_run_if(on_event::<u8>())
|
||||
.distributive_run_if(any_with_component::<TestComponent>())
|
||||
.distributive_run_if(not(run_once())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,57 +5,11 @@ use crate::{
|
|||
condition::{BoxedCondition, Condition},
|
||||
graph_utils::{Ambiguity, Dependency, DependencyKind, GraphInfo},
|
||||
set::{BoxedSystemSet, IntoSystemSet, SystemSet},
|
||||
ScheduleLabel,
|
||||
},
|
||||
system::{BoxedSystem, IntoSystem, System},
|
||||
};
|
||||
|
||||
use super::{BaseSystemSet, FreeSystemSet};
|
||||
|
||||
/// A [`SystemSet`] with scheduling metadata.
|
||||
pub struct SystemSetConfig {
|
||||
pub(super) set: BoxedSystemSet,
|
||||
pub(super) graph_info: GraphInfo,
|
||||
pub(super) conditions: Vec<BoxedCondition>,
|
||||
}
|
||||
|
||||
impl SystemSetConfig {
|
||||
fn new(set: BoxedSystemSet) -> Self {
|
||||
// system type sets are automatically populated
|
||||
// to avoid unintentionally broad changes, they cannot be configured
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"configuring system type sets is not allowed"
|
||||
);
|
||||
|
||||
Self {
|
||||
set,
|
||||
graph_info: GraphInfo::system_set(),
|
||||
conditions: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`System`] with scheduling metadata.
|
||||
pub struct SystemConfig {
|
||||
pub(super) system: BoxedSystem,
|
||||
pub(super) graph_info: GraphInfo,
|
||||
pub(super) conditions: Vec<BoxedCondition>,
|
||||
}
|
||||
|
||||
impl SystemConfig {
|
||||
fn new(system: BoxedSystem) -> Self {
|
||||
// include system in its default sets
|
||||
let sets = system.default_system_sets().into_iter().collect();
|
||||
let mut graph_info = GraphInfo::system();
|
||||
graph_info.sets = sets;
|
||||
Self {
|
||||
system,
|
||||
graph_info,
|
||||
conditions: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_condition<M>(condition: impl Condition<M>) -> BoxedCondition {
|
||||
let condition_system = IntoSystem::into_system(condition);
|
||||
assert!(
|
||||
|
@ -79,6 +33,370 @@ fn ambiguous_with(graph_info: &mut GraphInfo, set: BoxedSystemSet) {
|
|||
}
|
||||
}
|
||||
|
||||
impl<Marker, F> IntoSystemConfigs<Marker> for F
|
||||
where
|
||||
F: IntoSystem<(), (), Marker>,
|
||||
{
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
SystemConfigs::new_system(Box::new(IntoSystem::into_system(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfigs<()> for BoxedSystem<(), ()> {
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
SystemConfigs::new_system(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemConfig {
|
||||
pub(crate) system: BoxedSystem,
|
||||
pub(crate) graph_info: GraphInfo,
|
||||
pub(crate) conditions: Vec<BoxedCondition>,
|
||||
}
|
||||
|
||||
/// A collection of [`SystemConfig`].
|
||||
pub enum SystemConfigs {
|
||||
SystemConfig(SystemConfig),
|
||||
Configs {
|
||||
configs: Vec<SystemConfigs>,
|
||||
/// If `true`, adds `before -> after` ordering constraints between the successive elements.
|
||||
chained: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl SystemConfigs {
|
||||
fn new_system(system: BoxedSystem) -> Self {
|
||||
// include system in its default sets
|
||||
let sets = system.default_system_sets().into_iter().collect();
|
||||
Self::SystemConfig(SystemConfig {
|
||||
system,
|
||||
graph_info: GraphInfo {
|
||||
sets,
|
||||
..Default::default()
|
||||
},
|
||||
conditions: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
fn in_set_inner(&mut self, set: BoxedSystemSet) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
config.graph_info.sets.push(set);
|
||||
}
|
||||
SystemConfigs::Configs { configs, .. } => {
|
||||
for config in configs {
|
||||
config.in_set_inner(set.dyn_clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn before_inner(&mut self, set: BoxedSystemSet) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
config
|
||||
.graph_info
|
||||
.dependencies
|
||||
.push(Dependency::new(DependencyKind::Before, set));
|
||||
}
|
||||
SystemConfigs::Configs { configs, .. } => {
|
||||
for config in configs {
|
||||
config.before_inner(set.dyn_clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn after_inner(&mut self, set: BoxedSystemSet) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
config
|
||||
.graph_info
|
||||
.dependencies
|
||||
.push(Dependency::new(DependencyKind::After, set));
|
||||
}
|
||||
SystemConfigs::Configs { configs, .. } => {
|
||||
for config in configs {
|
||||
config.after_inner(set.dyn_clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn distributive_run_if_inner<M>(&mut self, condition: impl Condition<M> + Clone) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
config.conditions.push(new_condition(condition));
|
||||
}
|
||||
SystemConfigs::Configs { configs, .. } => {
|
||||
for config in configs {
|
||||
config.distributive_run_if_inner(condition.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ambiguous_with_inner(&mut self, set: BoxedSystemSet) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
ambiguous_with(&mut config.graph_info, set);
|
||||
}
|
||||
SystemConfigs::Configs { configs, .. } => {
|
||||
for config in configs {
|
||||
config.ambiguous_with_inner(set.dyn_clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ambiguous_with_all_inner(&mut self) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
config.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
|
||||
}
|
||||
SystemConfigs::Configs { configs, .. } => {
|
||||
for config in configs {
|
||||
config.ambiguous_with_all_inner();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_if_inner(&mut self, condition: BoxedCondition) {
|
||||
match self {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
config.conditions.push(condition);
|
||||
}
|
||||
SystemConfigs::Configs { .. } => {
|
||||
todo!("run_if is not implemented for groups of systems yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Types that can convert into a [`SystemConfigs`].
|
||||
pub trait IntoSystemConfigs<Marker>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Convert into a [`SystemConfigs`].
|
||||
#[doc(hidden)]
|
||||
fn into_configs(self) -> SystemConfigs;
|
||||
|
||||
/// Add these systems to the provided `set`.
|
||||
#[track_caller]
|
||||
fn in_set(self, set: impl SystemSet) -> SystemConfigs {
|
||||
self.into_configs().in_set(set)
|
||||
}
|
||||
|
||||
/// Run before all systems in `set`.
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().before(set)
|
||||
}
|
||||
|
||||
/// Run after all systems in `set`.
|
||||
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().after(set)
|
||||
}
|
||||
|
||||
/// Add a run condition to each contained system.
|
||||
///
|
||||
/// Each system will receive its own clone of the [`Condition`] and will only run
|
||||
/// if the `Condition` is true.
|
||||
///
|
||||
/// Each individual condition will be evaluated at most once (per schedule run),
|
||||
/// right before the corresponding system prepares to run.
|
||||
///
|
||||
/// This is equivalent to calling [`run_if`](IntoSystemConfigs::run_if) on each individual
|
||||
/// system, as shown below:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # fn a() {}
|
||||
/// # fn b() {}
|
||||
/// # fn condition() -> bool { true }
|
||||
/// app.add_systems((a, b).distributive_run_if(condition));
|
||||
/// app.add_systems((a.run_if(condition), b.run_if(condition)));
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Because the conditions are evaluated separately for each system, there is no guarantee
|
||||
/// that all evaluations in a single schedule run will yield the same result. If another
|
||||
/// system is run inbetween two evaluations it could cause the result of the condition to change.
|
||||
///
|
||||
/// Use [`run_if`](IntoSystemSetConfig::run_if) on a [`SystemSet`] if you want to make sure
|
||||
/// that either all or none of the systems are run, or you don't want to evaluate the run
|
||||
/// condition for each contained system separately.
|
||||
fn distributive_run_if<M>(self, condition: impl Condition<M> + Clone) -> SystemConfigs {
|
||||
self.into_configs().distributive_run_if(condition)
|
||||
}
|
||||
|
||||
/// Run the systems only if the [`Condition`] is `true`.
|
||||
///
|
||||
/// The `Condition` will be evaluated at most once (per schedule run),
|
||||
/// the first time a system in this set prepares to run.
|
||||
fn run_if<M>(self, condition: impl Condition<M>) -> SystemConfigs {
|
||||
self.into_configs().run_if(condition)
|
||||
}
|
||||
|
||||
/// Suppress warnings and errors that would result from these systems having ambiguities
|
||||
/// (conflicting access but indeterminate order) with systems in `set`.
|
||||
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().ambiguous_with(set)
|
||||
}
|
||||
|
||||
/// Suppress warnings and errors that would result from these systems having ambiguities
|
||||
/// (conflicting access but indeterminate order) with any other system.
|
||||
fn ambiguous_with_all(self) -> SystemConfigs {
|
||||
self.into_configs().ambiguous_with_all()
|
||||
}
|
||||
|
||||
/// Treat this collection as a sequence of systems.
|
||||
///
|
||||
/// Ordering constraints will be applied between the successive elements.
|
||||
fn chain(self) -> SystemConfigs {
|
||||
self.into_configs().chain()
|
||||
}
|
||||
|
||||
/// This used to add the system to `CoreSchedule::Startup`.
|
||||
/// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Always panics. Please migrate to the new `App::add_systems` with the `Startup` schedule:
|
||||
/// Ex: `app.add_system(foo.on_startup())` -> `app.add_systems(Startup, foo)`
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "`app.add_system(foo.on_startup())` has been deprecated in favor of `app.add_systems(Startup, foo)`. Please migrate to that API."
|
||||
)]
|
||||
fn on_startup(self) -> SystemConfigs {
|
||||
panic!("`app.add_system(foo.on_startup())` has been deprecated in favor of `app.add_systems(Startup, foo)`. Please migrate to that API.");
|
||||
}
|
||||
|
||||
/// This used to add the system to the provided `schedule`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Always panics. Please migrate to the new `App::add_systems`:
|
||||
/// Ex: `app.add_system(foo.in_schedule(SomeSchedule))` -> `app.add_systems(SomeSchedule, foo)`
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "`app.add_system(foo.in_schedule(SomeSchedule))` has been deprecated in favor of `app.add_systems(SomeSchedule, foo)`. Please migrate to that API."
|
||||
)]
|
||||
fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemConfigs {
|
||||
panic!("`app.add_system(foo.in_schedule(SomeSchedule))` has been deprecated in favor of `app.add_systems(SomeSchedule, foo)`. Please migrate to that API.");
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfigs<()> for SystemConfigs {
|
||||
fn into_configs(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"adding arbitrary systems to a system type set is not allowed"
|
||||
);
|
||||
|
||||
self.in_set_inner(set.dyn_clone());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
self.before_inner(set.dyn_clone());
|
||||
self
|
||||
}
|
||||
|
||||
fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
self.after_inner(set.dyn_clone());
|
||||
self
|
||||
}
|
||||
|
||||
fn distributive_run_if<M>(mut self, condition: impl Condition<M> + Clone) -> SystemConfigs {
|
||||
self.distributive_run_if_inner(condition);
|
||||
self
|
||||
}
|
||||
|
||||
fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
self.ambiguous_with_inner(set.dyn_clone());
|
||||
self
|
||||
}
|
||||
|
||||
fn ambiguous_with_all(mut self) -> Self {
|
||||
self.ambiguous_with_all_inner();
|
||||
self
|
||||
}
|
||||
|
||||
fn run_if<M>(mut self, condition: impl Condition<M>) -> SystemConfigs {
|
||||
self.run_if_inner(new_condition(condition));
|
||||
self
|
||||
}
|
||||
|
||||
fn chain(mut self) -> Self {
|
||||
match &mut self {
|
||||
SystemConfigs::SystemConfig(_) => { /* no op */ }
|
||||
SystemConfigs::Configs { chained, .. } => {
|
||||
*chained = true;
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SystemConfigTupleMarker;
|
||||
|
||||
macro_rules! impl_system_collection {
|
||||
($(($param: ident, $sys: ident)),*) => {
|
||||
impl<$($param, $sys),*> IntoSystemConfigs<(SystemConfigTupleMarker, $($param,)*)> for ($($sys,)*)
|
||||
where
|
||||
$($sys: IntoSystemConfigs<$param>),*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
let ($($sys,)*) = self;
|
||||
SystemConfigs::Configs {
|
||||
configs: vec![$($sys.into_configs(),)*],
|
||||
chained: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_tuples!(impl_system_collection, 1, 20, P, S);
|
||||
|
||||
/// A [`SystemSet`] with scheduling metadata.
|
||||
pub struct SystemSetConfig {
|
||||
pub(super) set: BoxedSystemSet,
|
||||
pub(super) graph_info: GraphInfo,
|
||||
pub(super) conditions: Vec<BoxedCondition>,
|
||||
}
|
||||
|
||||
impl SystemSetConfig {
|
||||
fn new(set: BoxedSystemSet) -> Self {
|
||||
// system type sets are automatically populated
|
||||
// to avoid unintentionally broad changes, they cannot be configured
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"configuring system type sets is not allowed"
|
||||
);
|
||||
|
||||
Self {
|
||||
set,
|
||||
graph_info: GraphInfo::default(),
|
||||
conditions: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Types that can be converted into a [`SystemSetConfig`].
|
||||
///
|
||||
/// This has been implemented for all types that implement [`SystemSet`] and boxed trait objects.
|
||||
|
@ -88,18 +406,9 @@ pub trait IntoSystemSetConfig: Sized {
|
|||
fn into_config(self) -> SystemSetConfig;
|
||||
/// Add to the provided `set`.
|
||||
#[track_caller]
|
||||
fn in_set(self, set: impl FreeSystemSet) -> SystemSetConfig {
|
||||
fn in_set(self, set: impl SystemSet) -> SystemSetConfig {
|
||||
self.into_config().in_set(set)
|
||||
}
|
||||
/// Add to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
|
||||
#[track_caller]
|
||||
fn in_base_set(self, set: impl BaseSystemSet) -> SystemSetConfig {
|
||||
self.into_config().in_base_set(set)
|
||||
}
|
||||
/// Add this set to the schedules's default base set.
|
||||
fn in_default_base_set(self) -> SystemSetConfig {
|
||||
self.into_config().in_default_base_set()
|
||||
}
|
||||
/// Run before all systems in `set`.
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfig {
|
||||
self.into_config().before(set)
|
||||
|
@ -125,6 +434,35 @@ pub trait IntoSystemSetConfig: Sized {
|
|||
fn ambiguous_with_all(self) -> SystemSetConfig {
|
||||
self.into_config().ambiguous_with_all()
|
||||
}
|
||||
|
||||
/// This used to configure the set in the `CoreSchedule::Startup` schedule.
|
||||
/// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Always panics. Please migrate to the new `App::configure_set` with the `Startup` schedule:
|
||||
/// Ex: `app.configure_set(MySet.on_startup())` -> `app.configure_set(Startup, MySet)`
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "`app.configure_set(MySet.on_startup())` has been deprecated in favor of `app.configure_set(Startup, MySet)`. Please migrate to that API."
|
||||
)]
|
||||
fn on_startup(self) -> SystemSetConfigs {
|
||||
panic!("`app.configure_set(MySet.on_startup())` has been deprecated in favor of `app.configure_set(Startup, MySet)`. Please migrate to that API.");
|
||||
}
|
||||
|
||||
/// This used to configure the set in the provided `schedule`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Always panics. Please migrate to the new `App::configure_set`:
|
||||
/// Ex: `app.configure_set(MySet.in_schedule(SomeSchedule))` -> `app.configure_set(SomeSchedule, MySet)`
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "`app.configure_set(MySet.in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_set(SomeSchedule, MySet)`. Please migrate to that API."
|
||||
)]
|
||||
fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemSetConfigs {
|
||||
panic!("`app.configure_set(MySet.in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_set(SomeSchedule, MySet)`. Please migrate to that API.");
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SystemSet> IntoSystemSetConfig for S {
|
||||
|
@ -150,41 +488,10 @@ impl IntoSystemSetConfig for SystemSetConfig {
|
|||
set.system_type().is_none(),
|
||||
"adding arbitrary systems to a system type set is not allowed"
|
||||
);
|
||||
assert!(
|
||||
!set.is_base(),
|
||||
"Sets cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
|
||||
);
|
||||
assert!(
|
||||
!self.set.is_base(),
|
||||
"Base system sets cannot be added to other sets."
|
||||
);
|
||||
self.graph_info.sets.push(Box::new(set));
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_base_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"System type sets cannot be base sets."
|
||||
);
|
||||
assert!(
|
||||
set.is_base(),
|
||||
"Sets cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
|
||||
);
|
||||
assert!(
|
||||
!self.set.is_base(),
|
||||
"Base system sets cannot be added to other sets."
|
||||
);
|
||||
self.graph_info.set_base_set(Box::new(set));
|
||||
self
|
||||
}
|
||||
|
||||
fn in_default_base_set(mut self) -> SystemSetConfig {
|
||||
self.graph_info.add_default_base_set = true;
|
||||
self
|
||||
}
|
||||
|
||||
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
self.graph_info.dependencies.push(Dependency::new(
|
||||
DependencyKind::Before,
|
||||
|
@ -217,329 +524,6 @@ impl IntoSystemSetConfig for SystemSetConfig {
|
|||
}
|
||||
}
|
||||
|
||||
/// Types that can be converted into a [`SystemConfig`].
|
||||
///
|
||||
/// This has been implemented for boxed [`System<In=(), Out=()>`](crate::system::System)
|
||||
/// trait objects and all functions that turn into such.
|
||||
pub trait IntoSystemConfig<Marker, Config = SystemConfig>: Sized
|
||||
where
|
||||
Config: IntoSystemConfig<(), Config>,
|
||||
{
|
||||
/// Convert into a [`SystemConfig`].
|
||||
#[doc(hidden)]
|
||||
fn into_config(self) -> Config;
|
||||
/// Add to `set` membership.
|
||||
#[track_caller]
|
||||
fn in_set(self, set: impl FreeSystemSet) -> Config {
|
||||
self.into_config().in_set(set)
|
||||
}
|
||||
/// Add to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
|
||||
#[track_caller]
|
||||
fn in_base_set(self, set: impl BaseSystemSet) -> Config {
|
||||
self.into_config().in_base_set(set)
|
||||
}
|
||||
/// Don't add this system to the schedules's default set.
|
||||
fn no_default_base_set(self) -> Config {
|
||||
self.into_config().no_default_base_set()
|
||||
}
|
||||
/// Run before all systems in `set`.
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> Config {
|
||||
self.into_config().before(set)
|
||||
}
|
||||
/// Run after all systems in `set`.
|
||||
fn after<M>(self, set: impl IntoSystemSet<M>) -> Config {
|
||||
self.into_config().after(set)
|
||||
}
|
||||
/// Run only if the [`Condition`] is `true`.
|
||||
///
|
||||
/// The `Condition` will be evaluated at most once (per schedule run),
|
||||
/// when the system prepares to run.
|
||||
fn run_if<M>(self, condition: impl Condition<M>) -> Config {
|
||||
self.into_config().run_if(condition)
|
||||
}
|
||||
/// Suppress warnings and errors that would result from this system having ambiguities
|
||||
/// (conflicting access but indeterminate order) with systems in `set`.
|
||||
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> Config {
|
||||
self.into_config().ambiguous_with(set)
|
||||
}
|
||||
/// Suppress warnings and errors that would result from this system having ambiguities
|
||||
/// (conflicting access but indeterminate order) with any other system.
|
||||
fn ambiguous_with_all(self) -> Config {
|
||||
self.into_config().ambiguous_with_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Marker, F> IntoSystemConfig<Marker> for F
|
||||
where
|
||||
F: IntoSystem<(), (), Marker>,
|
||||
{
|
||||
fn into_config(self) -> SystemConfig {
|
||||
SystemConfig::new(Box::new(IntoSystem::into_system(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfig<()> for BoxedSystem<(), ()> {
|
||||
fn into_config(self) -> SystemConfig {
|
||||
SystemConfig::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfig<()> for SystemConfig {
|
||||
fn into_config(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"adding arbitrary systems to a system type set is not allowed"
|
||||
);
|
||||
assert!(
|
||||
!set.is_base(),
|
||||
"Systems cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
|
||||
);
|
||||
self.graph_info.sets.push(Box::new(set));
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_base_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"System type sets cannot be base sets."
|
||||
);
|
||||
assert!(
|
||||
set.is_base(),
|
||||
"Systems cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
|
||||
);
|
||||
self.graph_info.set_base_set(Box::new(set));
|
||||
self
|
||||
}
|
||||
|
||||
fn no_default_base_set(mut self) -> SystemConfig {
|
||||
self.graph_info.add_default_base_set = false;
|
||||
self
|
||||
}
|
||||
|
||||
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
self.graph_info.dependencies.push(Dependency::new(
|
||||
DependencyKind::Before,
|
||||
Box::new(set.into_system_set()),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
self.graph_info.dependencies.push(Dependency::new(
|
||||
DependencyKind::After,
|
||||
Box::new(set.into_system_set()),
|
||||
));
|
||||
self
|
||||
}
|
||||
|
||||
fn run_if<M>(mut self, condition: impl Condition<M>) -> Self {
|
||||
self.conditions.push(new_condition(condition));
|
||||
self
|
||||
}
|
||||
|
||||
fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
ambiguous_with(&mut self.graph_info, Box::new(set.into_system_set()));
|
||||
self
|
||||
}
|
||||
|
||||
fn ambiguous_with_all(mut self) -> Self {
|
||||
self.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`SystemConfig`].
|
||||
pub struct SystemConfigs {
|
||||
pub(super) systems: Vec<SystemConfig>,
|
||||
/// If `true`, adds `before -> after` ordering constraints between the successive elements.
|
||||
pub(super) chained: bool,
|
||||
}
|
||||
|
||||
/// Types that can convert into a [`SystemConfigs`].
|
||||
pub trait IntoSystemConfigs<Marker>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
/// Convert into a [`SystemConfigs`].
|
||||
#[doc(hidden)]
|
||||
fn into_configs(self) -> SystemConfigs;
|
||||
|
||||
/// Add these systems to the provided `set`.
|
||||
#[track_caller]
|
||||
fn in_set(self, set: impl FreeSystemSet) -> SystemConfigs {
|
||||
self.into_configs().in_set(set)
|
||||
}
|
||||
|
||||
/// Add these systems to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
|
||||
#[track_caller]
|
||||
fn in_base_set(self, set: impl BaseSystemSet) -> SystemConfigs {
|
||||
self.into_configs().in_base_set(set)
|
||||
}
|
||||
|
||||
/// Run before all systems in `set`.
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().before(set)
|
||||
}
|
||||
|
||||
/// Run after all systems in `set`.
|
||||
fn after<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().after(set)
|
||||
}
|
||||
|
||||
/// Add a run condition to each contained system.
|
||||
///
|
||||
/// Each system will receive its own clone of the [`Condition`] and will only run
|
||||
/// if the `Condition` is true.
|
||||
///
|
||||
/// Each individual condition will be evaluated at most once (per schedule run),
|
||||
/// right before the corresponding system prepares to run.
|
||||
///
|
||||
/// This is equivalent to calling [`run_if`](IntoSystemConfig::run_if) on each individual
|
||||
/// system, as shown below:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut app = Schedule::new();
|
||||
/// # fn a() {}
|
||||
/// # fn b() {}
|
||||
/// # fn condition() -> bool { true }
|
||||
/// app.add_systems((a, b).distributive_run_if(condition));
|
||||
/// app.add_systems((a.run_if(condition), b.run_if(condition)));
|
||||
/// ```
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Because the conditions are evaluated separately for each system, there is no guarantee
|
||||
/// that all evaluations in a single schedule run will yield the same result. If another
|
||||
/// system is run inbetween two evaluations it could cause the result of the condition to change.
|
||||
///
|
||||
/// Use [`run_if`](IntoSystemSetConfig::run_if) on a [`SystemSet`] if you want to make sure
|
||||
/// that either all or none of the systems are run, or you don't want to evaluate the run
|
||||
/// condition for each contained system separately.
|
||||
fn distributive_run_if<M>(self, condition: impl Condition<M> + Clone) -> SystemConfigs {
|
||||
self.into_configs().distributive_run_if(condition)
|
||||
}
|
||||
|
||||
/// Suppress warnings and errors that would result from these systems having ambiguities
|
||||
/// (conflicting access but indeterminate order) with systems in `set`.
|
||||
fn ambiguous_with<M>(self, set: impl IntoSystemSet<M>) -> SystemConfigs {
|
||||
self.into_configs().ambiguous_with(set)
|
||||
}
|
||||
|
||||
/// Suppress warnings and errors that would result from these systems having ambiguities
|
||||
/// (conflicting access but indeterminate order) with any other system.
|
||||
fn ambiguous_with_all(self) -> SystemConfigs {
|
||||
self.into_configs().ambiguous_with_all()
|
||||
}
|
||||
|
||||
/// Treat this collection as a sequence of systems.
|
||||
///
|
||||
/// Ordering constraints will be applied between the successive elements.
|
||||
fn chain(self) -> SystemConfigs {
|
||||
self.into_configs().chain()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemConfigs<()> for SystemConfigs {
|
||||
fn into_configs(self) -> Self {
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"adding arbitrary systems to a system type set is not allowed"
|
||||
);
|
||||
assert!(
|
||||
!set.is_base(),
|
||||
"Systems cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
|
||||
);
|
||||
for config in &mut self.systems {
|
||||
config.graph_info.sets.push(set.dyn_clone());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_base_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"System type sets cannot be base sets."
|
||||
);
|
||||
assert!(
|
||||
set.is_base(),
|
||||
"Systems cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
|
||||
);
|
||||
for config in &mut self.systems {
|
||||
config.graph_info.set_base_set(set.dyn_clone());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
for config in &mut self.systems {
|
||||
config
|
||||
.graph_info
|
||||
.dependencies
|
||||
.push(Dependency::new(DependencyKind::Before, set.dyn_clone()));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn after<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
for config in &mut self.systems {
|
||||
config
|
||||
.graph_info
|
||||
.dependencies
|
||||
.push(Dependency::new(DependencyKind::After, set.dyn_clone()));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn distributive_run_if<M>(mut self, condition: impl Condition<M> + Clone) -> SystemConfigs {
|
||||
for config in &mut self.systems {
|
||||
config.conditions.push(new_condition(condition.clone()));
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn ambiguous_with<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
for config in &mut self.systems {
|
||||
ambiguous_with(&mut config.graph_info, set.dyn_clone());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn ambiguous_with_all(mut self) -> Self {
|
||||
for config in &mut self.systems {
|
||||
config.graph_info.ambiguous_with = Ambiguity::IgnoreAll;
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn chain(mut self) -> Self {
|
||||
self.chained = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of [`SystemSetConfig`].
|
||||
pub struct SystemSetConfigs {
|
||||
pub(super) sets: Vec<SystemSetConfig>,
|
||||
|
@ -558,16 +542,10 @@ where
|
|||
|
||||
/// Add these system sets to the provided `set`.
|
||||
#[track_caller]
|
||||
fn in_set(self, set: impl FreeSystemSet) -> SystemSetConfigs {
|
||||
fn in_set(self, set: impl SystemSet) -> SystemSetConfigs {
|
||||
self.into_configs().in_set(set)
|
||||
}
|
||||
|
||||
/// Add these system sets to the provided "base" `set`. For more information on base sets, see [`SystemSet::is_base`].
|
||||
#[track_caller]
|
||||
fn in_base_set(self, set: impl BaseSystemSet) -> SystemSetConfigs {
|
||||
self.into_configs().in_base_set(set)
|
||||
}
|
||||
|
||||
/// Run before all systems in `set`.
|
||||
fn before<M>(self, set: impl IntoSystemSet<M>) -> SystemSetConfigs {
|
||||
self.into_configs().before(set)
|
||||
|
@ -596,6 +574,35 @@ where
|
|||
fn chain(self) -> SystemSetConfigs {
|
||||
self.into_configs().chain()
|
||||
}
|
||||
|
||||
/// This used to configure the sets in the `CoreSchedule::Startup` schedule.
|
||||
/// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Always panics. Please migrate to the new `App::configure_sets` with the `Startup` schedule:
|
||||
/// Ex: `app.configure_sets((A, B).on_startup())` -> `app.configure_sets(Startup, (A, B))`
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "`app.configure_sets((A, B).on_startup())` has been deprecated in favor of `app.configure_sets(Startup, (A, B))`. Please migrate to that API."
|
||||
)]
|
||||
fn on_startup(self) -> SystemSetConfigs {
|
||||
panic!("`app.configure_sets((A, B).on_startup())` has been deprecated in favor of `app.configure_sets(Startup, (A, B))`. Please migrate to that API.");
|
||||
}
|
||||
|
||||
/// This used to configure the sets in the provided `schedule`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Always panics. Please migrate to the new `App::configure_set`:
|
||||
/// Ex: `app.configure_sets((A, B).in_schedule(SomeSchedule))` -> `app.configure_sets(SomeSchedule, (A, B))`
|
||||
#[deprecated(
|
||||
since = "0.11.0",
|
||||
note = "`app.configure_sets((A, B).in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_sets(SomeSchedule, (A, B))`. Please migrate to that API."
|
||||
)]
|
||||
fn in_schedule(self, _schedule: impl ScheduleLabel) -> SystemSetConfigs {
|
||||
panic!("`app.configure_sets((A, B).in_schedule(SomeSchedule))` has been deprecated in favor of `app.configure_sets(SomeSchedule, (A, B))`. Please migrate to that API.");
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoSystemSetConfigs for SystemSetConfigs {
|
||||
|
@ -609,42 +616,13 @@ impl IntoSystemSetConfigs for SystemSetConfigs {
|
|||
set.system_type().is_none(),
|
||||
"adding arbitrary systems to a system type set is not allowed"
|
||||
);
|
||||
assert!(
|
||||
!set.is_base(),
|
||||
"Sets cannot be added to 'base' system sets using 'in_set'. Use 'in_base_set' instead."
|
||||
);
|
||||
for config in &mut self.sets {
|
||||
assert!(
|
||||
!config.set.is_base(),
|
||||
"Base system sets cannot be added to other sets."
|
||||
);
|
||||
config.graph_info.sets.push(set.dyn_clone());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn in_base_set(mut self, set: impl SystemSet) -> Self {
|
||||
assert!(
|
||||
set.system_type().is_none(),
|
||||
"System type sets cannot be base sets."
|
||||
);
|
||||
assert!(
|
||||
set.is_base(),
|
||||
"Sets cannot be added to normal sets using 'in_base_set'. Use 'in_set' instead."
|
||||
);
|
||||
for config in &mut self.sets {
|
||||
assert!(
|
||||
!config.set.is_base(),
|
||||
"Base system sets cannot be added to other sets."
|
||||
);
|
||||
config.graph_info.set_base_set(set.dyn_clone());
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn before<M>(mut self, set: impl IntoSystemSet<M>) -> Self {
|
||||
let set = set.into_system_set();
|
||||
for config in &mut self.sets {
|
||||
|
@ -692,24 +670,6 @@ impl IntoSystemSetConfigs for SystemSetConfigs {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_system_collection {
|
||||
($(($param: ident, $sys: ident)),*) => {
|
||||
impl<$($param, $sys),*> IntoSystemConfigs<($($param,)*)> for ($($sys,)*)
|
||||
where
|
||||
$($sys: IntoSystemConfig<$param>),*
|
||||
{
|
||||
#[allow(non_snake_case)]
|
||||
fn into_configs(self) -> SystemConfigs {
|
||||
let ($($sys,)*) = self;
|
||||
SystemConfigs {
|
||||
systems: vec![$($sys.into_config(),)*],
|
||||
chained: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_system_set_collection {
|
||||
($($set: ident),*) => {
|
||||
impl<$($set: IntoSystemSetConfig),*> IntoSystemSetConfigs for ($($set,)*)
|
||||
|
@ -726,5 +686,4 @@ macro_rules! impl_system_set_collection {
|
|||
}
|
||||
}
|
||||
|
||||
all_tuples!(impl_system_collection, 0, 15, P, S);
|
||||
all_tuples!(impl_system_set_collection, 0, 15, S);
|
||||
|
|
|
@ -67,56 +67,14 @@ pub(crate) enum Ambiguity {
|
|||
IgnoreAll,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct GraphInfo {
|
||||
pub(crate) sets: Vec<BoxedSystemSet>,
|
||||
pub(crate) dependencies: Vec<Dependency>,
|
||||
pub(crate) ambiguous_with: Ambiguity,
|
||||
pub(crate) add_default_base_set: bool,
|
||||
pub(crate) base_set: Option<BoxedSystemSet>,
|
||||
}
|
||||
|
||||
impl Default for GraphInfo {
|
||||
fn default() -> Self {
|
||||
GraphInfo {
|
||||
sets: Vec::new(),
|
||||
base_set: None,
|
||||
dependencies: Vec::new(),
|
||||
ambiguous_with: Ambiguity::default(),
|
||||
add_default_base_set: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphInfo {
|
||||
pub(crate) fn system() -> GraphInfo {
|
||||
GraphInfo {
|
||||
// systems get the default base set automatically
|
||||
add_default_base_set: true,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn system_set() -> GraphInfo {
|
||||
GraphInfo {
|
||||
// sets do not get the default base set automatically
|
||||
add_default_base_set: false,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn set_base_set(&mut self, set: BoxedSystemSet) {
|
||||
if let Some(current) = &self.base_set {
|
||||
panic!(
|
||||
"Cannot set the base set because base set {current:?} has already been configured."
|
||||
);
|
||||
} else {
|
||||
self.base_set = Some(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts 2D row-major pair of indices into a 1D array index.
|
||||
pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize {
|
||||
debug_assert!(col < num_cols);
|
||||
|
|
|
@ -23,7 +23,7 @@ mod tests {
|
|||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
pub use crate as bevy_ecs;
|
||||
pub use crate::schedule::{IntoSystemConfig, IntoSystemSetConfig, Schedule, SystemSet};
|
||||
pub use crate::schedule::{IntoSystemSetConfig, Schedule, SystemSet};
|
||||
pub use crate::system::{Res, ResMut};
|
||||
pub use crate::{prelude::World, system::Resource};
|
||||
|
||||
|
@ -75,7 +75,7 @@ mod tests {
|
|||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_system(make_function_system(0));
|
||||
schedule.add_systems(make_function_system(0));
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
||||
|
@ -88,7 +88,7 @@ mod tests {
|
|||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_system(make_exclusive_system(0));
|
||||
schedule.add_systems(make_exclusive_system(0));
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
||||
|
@ -108,7 +108,7 @@ mod tests {
|
|||
|
||||
for _ in 0..thread_count {
|
||||
let inner = barrier.clone();
|
||||
schedule.add_system(move || {
|
||||
schedule.add_systems(move || {
|
||||
inner.wait();
|
||||
});
|
||||
}
|
||||
|
@ -127,13 +127,13 @@ mod tests {
|
|||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_system(named_system);
|
||||
schedule.add_system(make_function_system(1).before(named_system));
|
||||
schedule.add_system(
|
||||
schedule.add_systems((
|
||||
named_system,
|
||||
make_function_system(1).before(named_system),
|
||||
make_function_system(0)
|
||||
.after(named_system)
|
||||
.in_set(TestSet::A),
|
||||
);
|
||||
));
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![1, u32::MAX, 0]);
|
||||
|
@ -144,12 +144,12 @@ mod tests {
|
|||
|
||||
// modify the schedule after it's been initialized and test ordering with sets
|
||||
schedule.configure_set(TestSet::A.after(named_system));
|
||||
schedule.add_system(
|
||||
schedule.add_systems((
|
||||
make_function_system(3)
|
||||
.before(TestSet::A)
|
||||
.after(named_system),
|
||||
);
|
||||
schedule.add_system(make_function_system(4).after(TestSet::A));
|
||||
make_function_system(4).after(TestSet::A),
|
||||
));
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(
|
||||
|
@ -195,6 +195,50 @@ mod tests {
|
|||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![0, 1, 2, 3]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_systems_correct_order_nested() {
|
||||
let mut world = World::new();
|
||||
let mut schedule = Schedule::new();
|
||||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_systems(
|
||||
(
|
||||
(make_function_system(0), make_function_system(1)).chain(),
|
||||
make_function_system(2),
|
||||
(make_function_system(3), make_function_system(4)).chain(),
|
||||
(
|
||||
make_function_system(5),
|
||||
(make_function_system(6), make_function_system(7)),
|
||||
),
|
||||
(
|
||||
(make_function_system(8), make_function_system(9)).chain(),
|
||||
make_function_system(10),
|
||||
),
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
|
||||
schedule.run(&mut world);
|
||||
let order = &world.resource::<SystemOrder>().0;
|
||||
assert_eq!(
|
||||
&order[0..5],
|
||||
&[0, 1, 2, 3, 4],
|
||||
"first five items should be exactly ordered"
|
||||
);
|
||||
let unordered = &order[5..8];
|
||||
assert!(
|
||||
unordered.contains(&5) && unordered.contains(&6) && unordered.contains(&7),
|
||||
"unordered must be 5, 6, and 7 in any order"
|
||||
);
|
||||
let partially_ordered = &order[8..11];
|
||||
assert!(
|
||||
partially_ordered == [8, 9, 10] || partially_ordered == [10, 8, 9],
|
||||
"partially_ordered must be [8, 9, 10] or [10, 8, 9]"
|
||||
);
|
||||
assert!(order.len() == 11, "must have exacty 11 order entries");
|
||||
}
|
||||
}
|
||||
|
||||
mod conditions {
|
||||
|
@ -210,7 +254,7 @@ mod tests {
|
|||
world.init_resource::<RunConditionBool>();
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_system(
|
||||
schedule.add_systems(
|
||||
make_function_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
|
||||
);
|
||||
|
||||
|
@ -256,7 +300,7 @@ mod tests {
|
|||
world.init_resource::<RunConditionBool>();
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_system(
|
||||
schedule.add_systems(
|
||||
make_exclusive_system(0).run_if(|condition: Res<RunConditionBool>| condition.0),
|
||||
);
|
||||
|
||||
|
@ -275,10 +319,12 @@ mod tests {
|
|||
|
||||
world.init_resource::<Counter>();
|
||||
|
||||
schedule.add_system(counting_system.run_if(|| false).run_if(|| false));
|
||||
schedule.add_system(counting_system.run_if(|| true).run_if(|| false));
|
||||
schedule.add_system(counting_system.run_if(|| false).run_if(|| true));
|
||||
schedule.add_system(counting_system.run_if(|| true).run_if(|| true));
|
||||
schedule.add_systems((
|
||||
counting_system.run_if(|| false).run_if(|| false),
|
||||
counting_system.run_if(|| true).run_if(|| false),
|
||||
counting_system.run_if(|| false).run_if(|| true),
|
||||
counting_system.run_if(|| true).run_if(|| true),
|
||||
));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
||||
|
@ -292,13 +338,13 @@ mod tests {
|
|||
world.init_resource::<Counter>();
|
||||
|
||||
schedule.configure_set(TestSet::A.run_if(|| false).run_if(|| false));
|
||||
schedule.add_system(counting_system.in_set(TestSet::A));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::A));
|
||||
schedule.configure_set(TestSet::B.run_if(|| true).run_if(|| false));
|
||||
schedule.add_system(counting_system.in_set(TestSet::B));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::B));
|
||||
schedule.configure_set(TestSet::C.run_if(|| false).run_if(|| true));
|
||||
schedule.add_system(counting_system.in_set(TestSet::C));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::C));
|
||||
schedule.configure_set(TestSet::D.run_if(|| true).run_if(|| true));
|
||||
schedule.add_system(counting_system.in_set(TestSet::D));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::D));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
||||
|
@ -312,13 +358,13 @@ mod tests {
|
|||
world.init_resource::<Counter>();
|
||||
|
||||
schedule.configure_set(TestSet::A.run_if(|| false));
|
||||
schedule.add_system(counting_system.in_set(TestSet::A).run_if(|| false));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::A).run_if(|| false));
|
||||
schedule.configure_set(TestSet::B.run_if(|| true));
|
||||
schedule.add_system(counting_system.in_set(TestSet::B).run_if(|| false));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::B).run_if(|| false));
|
||||
schedule.configure_set(TestSet::C.run_if(|| false));
|
||||
schedule.add_system(counting_system.in_set(TestSet::C).run_if(|| true));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::C).run_if(|| true));
|
||||
schedule.configure_set(TestSet::D.run_if(|| true));
|
||||
schedule.add_system(counting_system.in_set(TestSet::D).run_if(|| true));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::D).run_if(|| true));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Counter>().0.load(Ordering::Relaxed), 1);
|
||||
|
@ -335,7 +381,7 @@ mod tests {
|
|||
world.init_resource::<Bool2>();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
schedule.add_system(
|
||||
schedule.add_systems(
|
||||
counting_system
|
||||
.run_if(|res1: Res<RunConditionBool>| res1.is_changed())
|
||||
.run_if(|res2: Res<Bool2>| res2.is_changed()),
|
||||
|
@ -389,7 +435,7 @@ mod tests {
|
|||
.run_if(|res2: Res<Bool2>| res2.is_changed()),
|
||||
);
|
||||
|
||||
schedule.add_system(counting_system.in_set(TestSet::A));
|
||||
schedule.add_systems(counting_system.in_set(TestSet::A));
|
||||
|
||||
// both resource were just added.
|
||||
schedule.run(&mut world);
|
||||
|
@ -436,7 +482,7 @@ mod tests {
|
|||
schedule
|
||||
.configure_set(TestSet::A.run_if(|res1: Res<RunConditionBool>| res1.is_changed()));
|
||||
|
||||
schedule.add_system(
|
||||
schedule.add_systems(
|
||||
counting_system
|
||||
.run_if(|res2: Res<Bool2>| res2.is_changed())
|
||||
.in_set(TestSet::A),
|
||||
|
@ -535,15 +581,14 @@ mod tests {
|
|||
let mut schedule = Schedule::new();
|
||||
|
||||
// Schedule `bar` to run after `foo`.
|
||||
schedule.add_system(foo);
|
||||
schedule.add_system(bar.after(foo));
|
||||
schedule.add_systems((foo, bar.after(foo)));
|
||||
|
||||
// There's only one `foo`, so it's fine.
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Schedule another `foo`.
|
||||
schedule.add_system(foo);
|
||||
schedule.add_systems(foo);
|
||||
|
||||
// When there are multiple instances of `foo`, dependencies on
|
||||
// `foo` are no longer allowed. Too much ambiguity.
|
||||
|
@ -555,11 +600,11 @@ mod tests {
|
|||
|
||||
// same goes for `ambiguous_with`
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(foo);
|
||||
schedule.add_system(bar.ambiguous_with(foo));
|
||||
schedule.add_systems(foo);
|
||||
schedule.add_systems(bar.ambiguous_with(foo));
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(result.is_ok());
|
||||
schedule.add_system(foo);
|
||||
schedule.add_systems(foo);
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(matches!(
|
||||
result,
|
||||
|
@ -625,7 +670,7 @@ mod tests {
|
|||
fn foo() {}
|
||||
|
||||
// Add `foo` to both `A` and `C`.
|
||||
schedule.add_system(foo.in_set(TestSet::A).in_set(TestSet::C));
|
||||
schedule.add_systems(foo.in_set(TestSet::A).in_set(TestSet::C));
|
||||
|
||||
// Order `A -> B -> C`.
|
||||
schedule.configure_sets((
|
||||
|
@ -663,138 +708,4 @@ mod tests {
|
|||
assert!(matches!(result, Err(ScheduleBuildError::Ambiguity)));
|
||||
}
|
||||
}
|
||||
|
||||
mod base_sets {
|
||||
use super::*;
|
||||
|
||||
#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)]
|
||||
#[system_set(base)]
|
||||
enum Base {
|
||||
A,
|
||||
B,
|
||||
}
|
||||
|
||||
#[derive(SystemSet, Hash, Debug, Eq, PartialEq, Clone)]
|
||||
enum Normal {
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn disallow_adding_base_sets_to_sets() {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.configure_set(Base::A.in_set(Normal::X));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn disallow_adding_base_sets_to_base_sets() {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.configure_set(Base::A.in_base_set(Base::B));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn disallow_adding_set_to_multiple_base_sets() {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.configure_set(Normal::X.in_base_set(Base::A).in_base_set(Base::B));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn disallow_adding_sets_to_multiple_base_sets() {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.configure_sets(
|
||||
(Normal::X, Normal::Y)
|
||||
.in_base_set(Base::A)
|
||||
.in_base_set(Base::B),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn disallow_adding_system_to_multiple_base_sets() {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_system(named_system.in_base_set(Base::A).in_base_set(Base::B));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn disallow_adding_systems_to_multiple_base_sets() {
|
||||
let mut schedule = Schedule::new();
|
||||
schedule.add_systems(
|
||||
(make_function_system(0), make_function_system(1))
|
||||
.in_base_set(Base::A)
|
||||
.in_base_set(Base::B),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disallow_multiple_base_sets() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule
|
||||
.configure_set(Normal::X.in_base_set(Base::A))
|
||||
.configure_set(Normal::Y.in_base_set(Base::B))
|
||||
.add_system(named_system.in_set(Normal::X).in_set(Normal::Y));
|
||||
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ScheduleBuildError::SystemInMultipleBaseSets { .. })
|
||||
));
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule
|
||||
.configure_set(Normal::X.in_base_set(Base::A))
|
||||
.configure_set(Normal::Y.in_base_set(Base::B).in_set(Normal::X));
|
||||
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(matches!(
|
||||
result,
|
||||
Err(ScheduleBuildError::SetInMultipleBaseSets { .. })
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allow_same_base_sets() {
|
||||
let mut world = World::new();
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule
|
||||
.configure_set(Normal::X.in_base_set(Base::A))
|
||||
.configure_set(Normal::Y.in_base_set(Base::A))
|
||||
.add_system(named_system.in_set(Normal::X).in_set(Normal::Y));
|
||||
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(matches!(result, Ok(())));
|
||||
|
||||
let mut schedule = Schedule::new();
|
||||
schedule
|
||||
.configure_set(Normal::X.in_base_set(Base::A))
|
||||
.configure_set(Normal::Y.in_base_set(Base::A).in_set(Normal::X));
|
||||
|
||||
let result = schedule.initialize(&mut world);
|
||||
assert!(matches!(result, Ok(())));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_base_set_ordering() {
|
||||
let mut world = World::default();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule
|
||||
.set_default_base_set(Base::A)
|
||||
.configure_set(Base::A.before(Base::B))
|
||||
.add_system(make_function_system(0).in_base_set(Base::B))
|
||||
.add_system(make_function_system(1));
|
||||
schedule.run(&mut world);
|
||||
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![1, 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use fixedbitset::FixedBitSet;
|
|||
|
||||
use crate::{
|
||||
self as bevy_ecs,
|
||||
component::{ComponentId, Components},
|
||||
component::{ComponentId, Components, Tick},
|
||||
schedule::*,
|
||||
system::{BoxedSystem, Resource, System},
|
||||
world::World,
|
||||
|
@ -43,17 +43,11 @@ impl Schedules {
|
|||
/// and the old schedule is returned. Otherwise, `None` is returned.
|
||||
pub fn insert(&mut self, label: impl ScheduleLabel, schedule: Schedule) -> Option<Schedule> {
|
||||
let label = label.dyn_clone();
|
||||
if self.inner.contains_key(&label) {
|
||||
warn!("schedule with label {:?} already exists", label);
|
||||
}
|
||||
self.inner.insert(label, schedule)
|
||||
}
|
||||
|
||||
/// Removes the schedule corresponding to the `label` from the map, returning it if it existed.
|
||||
pub fn remove(&mut self, label: &dyn ScheduleLabel) -> Option<Schedule> {
|
||||
if !self.inner.contains_key(label) {
|
||||
warn!("schedule with label {:?} not found", label);
|
||||
}
|
||||
self.inner.remove(label)
|
||||
}
|
||||
|
||||
|
@ -62,9 +56,6 @@ impl Schedules {
|
|||
&mut self,
|
||||
label: &dyn ScheduleLabel,
|
||||
) -> Option<(Box<dyn ScheduleLabel>, Schedule)> {
|
||||
if !self.inner.contains_key(label) {
|
||||
warn!("schedule with label {:?} not found", label);
|
||||
}
|
||||
self.inner.remove_entry(label)
|
||||
}
|
||||
|
||||
|
@ -99,7 +90,7 @@ impl Schedules {
|
|||
/// Iterates the change ticks of all systems in all stored schedules and clamps any older than
|
||||
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
|
||||
/// This prevents overflow and thus prevents false positives.
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
#[cfg(feature = "trace")]
|
||||
let _all_span = info_span!("check stored schedule ticks").entered();
|
||||
// label used when trace feature is enabled
|
||||
|
@ -134,7 +125,7 @@ fn make_executor(kind: ExecutorKind) -> Box<dyn SystemExecutor> {
|
|||
/// fn main() {
|
||||
/// let mut world = World::new();
|
||||
/// let mut schedule = Schedule::default();
|
||||
/// schedule.add_system(hello_world);
|
||||
/// schedule.add_systems(hello_world);
|
||||
///
|
||||
/// schedule.run(&mut world);
|
||||
/// }
|
||||
|
@ -183,21 +174,16 @@ impl Schedule {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_default_base_set(&mut self, default_base_set: impl SystemSet) -> &mut Self {
|
||||
self.graph
|
||||
.set_default_base_set(Some(Box::new(default_base_set)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a system to the schedule.
|
||||
pub fn add_system<M>(&mut self, system: impl IntoSystemConfig<M>) -> &mut Self {
|
||||
self.graph.add_system(system);
|
||||
#[deprecated(since = "0.11.0", note = "please use `add_systems` instead")]
|
||||
pub fn add_system<M>(&mut self, system: impl IntoSystemConfigs<M>) -> &mut Self {
|
||||
self.graph.add_systems_inner(system.into_configs(), false);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a collection of systems to the schedule.
|
||||
pub fn add_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) -> &mut Self {
|
||||
self.graph.add_systems(systems);
|
||||
self.graph.add_systems_inner(systems.into_configs(), false);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -283,7 +269,7 @@ impl Schedule {
|
|||
/// Iterates the change ticks of all systems in the schedule and clamps any older than
|
||||
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
|
||||
/// This prevents overflow and thus prevents false positives.
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for system in &mut self.executable.systems {
|
||||
system.check_change_tick(change_tick);
|
||||
}
|
||||
|
@ -346,29 +332,14 @@ impl Dag {
|
|||
}
|
||||
}
|
||||
|
||||
/// Describes which base set (i.e. [`SystemSet`] where [`SystemSet::is_base`] returns true)
|
||||
/// a system belongs to.
|
||||
///
|
||||
/// Note that this is only populated once [`ScheduleGraph::build_schedule`] is called.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum BaseSetMembership {
|
||||
Uncalculated,
|
||||
None,
|
||||
Some(NodeId),
|
||||
}
|
||||
|
||||
/// A [`SystemSet`] with metadata, stored in a [`ScheduleGraph`].
|
||||
struct SystemSetNode {
|
||||
inner: BoxedSystemSet,
|
||||
base_set_membership: BaseSetMembership,
|
||||
}
|
||||
|
||||
impl SystemSetNode {
|
||||
pub fn new(set: BoxedSystemSet) -> Self {
|
||||
Self {
|
||||
inner: set,
|
||||
base_set_membership: BaseSetMembership::Uncalculated,
|
||||
}
|
||||
Self { inner: set }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
|
@ -383,14 +354,12 @@ impl SystemSetNode {
|
|||
/// A [`BoxedSystem`] with metadata, stored in a [`ScheduleGraph`].
|
||||
struct SystemNode {
|
||||
inner: Option<BoxedSystem>,
|
||||
base_set_membership: BaseSetMembership,
|
||||
}
|
||||
|
||||
impl SystemNode {
|
||||
pub fn new(system: BoxedSystem) -> Self {
|
||||
Self {
|
||||
inner: Some(system),
|
||||
base_set_membership: BaseSetMembership::Uncalculated,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,10 +370,6 @@ impl SystemNode {
|
|||
pub fn get_mut(&mut self) -> Option<&mut BoxedSystem> {
|
||||
self.inner.as_mut()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
format!("{:?}", &self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata for a [`Schedule`].
|
||||
|
@ -416,7 +381,6 @@ pub struct ScheduleGraph {
|
|||
system_set_conditions: Vec<Option<Vec<BoxedCondition>>>,
|
||||
system_set_ids: HashMap<BoxedSystemSet, NodeId>,
|
||||
uninit: Vec<(NodeId, usize)>,
|
||||
maybe_default_base_set: Vec<NodeId>,
|
||||
hierarchy: Dag,
|
||||
dependency: Dag,
|
||||
dependency_flattened: Dag,
|
||||
|
@ -426,7 +390,6 @@ pub struct ScheduleGraph {
|
|||
conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
|
||||
changed: bool,
|
||||
settings: ScheduleBuildSettings,
|
||||
default_base_set: Option<BoxedSystemSet>,
|
||||
}
|
||||
|
||||
impl ScheduleGraph {
|
||||
|
@ -437,7 +400,6 @@ impl ScheduleGraph {
|
|||
system_sets: Vec::new(),
|
||||
system_set_conditions: Vec::new(),
|
||||
system_set_ids: HashMap::new(),
|
||||
maybe_default_base_set: Vec::new(),
|
||||
uninit: Vec::new(),
|
||||
hierarchy: Dag::new(),
|
||||
dependency: Dag::new(),
|
||||
|
@ -448,7 +410,6 @@ impl ScheduleGraph {
|
|||
conflicting_systems: Vec::new(),
|
||||
changed: false,
|
||||
settings: default(),
|
||||
default_base_set: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -491,44 +452,29 @@ impl ScheduleGraph {
|
|||
}
|
||||
|
||||
/// Returns an iterator over all systems in this schedule.
|
||||
///
|
||||
/// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called.
|
||||
pub fn systems(
|
||||
&self,
|
||||
) -> impl Iterator<
|
||||
Item = (
|
||||
NodeId,
|
||||
&dyn System<In = (), Out = ()>,
|
||||
BaseSetMembership,
|
||||
&[BoxedCondition],
|
||||
),
|
||||
> {
|
||||
) -> impl Iterator<Item = (NodeId, &dyn System<In = (), Out = ()>, &[BoxedCondition])> {
|
||||
self.systems
|
||||
.iter()
|
||||
.zip(self.system_conditions.iter())
|
||||
.enumerate()
|
||||
.filter_map(|(i, (system_node, condition))| {
|
||||
let system = system_node.inner.as_deref()?;
|
||||
let base_set_membership = system_node.base_set_membership;
|
||||
let condition = condition.as_ref()?.as_slice();
|
||||
Some((NodeId::System(i), system, base_set_membership, condition))
|
||||
Some((NodeId::System(i), system, condition))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all system sets in this schedule.
|
||||
///
|
||||
/// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called.
|
||||
pub fn system_sets(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (NodeId, &dyn SystemSet, BaseSetMembership, &[BoxedCondition])> {
|
||||
pub fn system_sets(&self) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[BoxedCondition])> {
|
||||
self.system_set_ids.iter().map(|(_, node_id)| {
|
||||
let set_node = &self.system_sets[node_id.index()];
|
||||
let set = &*set_node.inner;
|
||||
let base_set_membership = set_node.base_set_membership;
|
||||
let conditions = self.system_set_conditions[node_id.index()]
|
||||
.as_deref()
|
||||
.unwrap_or(&[]);
|
||||
(*node_id, set, base_set_membership, conditions)
|
||||
(*node_id, set, conditions)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -556,47 +502,144 @@ impl ScheduleGraph {
|
|||
&self.conflicting_systems
|
||||
}
|
||||
|
||||
fn add_systems<M>(&mut self, systems: impl IntoSystemConfigs<M>) {
|
||||
let SystemConfigs { systems, chained } = systems.into_configs();
|
||||
let mut system_iter = systems.into_iter();
|
||||
if chained {
|
||||
let Some(prev) = system_iter.next() else { return };
|
||||
let mut prev_id = self.add_system_inner(prev).unwrap();
|
||||
for next in system_iter {
|
||||
let next_id = self.add_system_inner(next).unwrap();
|
||||
self.dependency.graph.add_edge(prev_id, next_id, ());
|
||||
prev_id = next_id;
|
||||
/// Adds the systems to the graph. Returns a vector of all node ids contained the nested `SystemConfigs`
|
||||
/// if `ancestor_chained` is true. Also returns true if "densely chained", meaning that all nested items
|
||||
/// are linearly chained in the order they are defined
|
||||
fn add_systems_inner(
|
||||
&mut self,
|
||||
configs: SystemConfigs,
|
||||
ancestor_chained: bool,
|
||||
) -> AddSystemsInnerResult {
|
||||
match configs {
|
||||
SystemConfigs::SystemConfig(config) => {
|
||||
let node_id = self.add_system_inner(config).unwrap();
|
||||
if ancestor_chained {
|
||||
AddSystemsInnerResult {
|
||||
densely_chained: true,
|
||||
nodes: vec![node_id],
|
||||
}
|
||||
} else {
|
||||
AddSystemsInnerResult {
|
||||
densely_chained: true,
|
||||
nodes: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for system in system_iter {
|
||||
self.add_system_inner(system).unwrap();
|
||||
SystemConfigs::Configs { configs, chained } => {
|
||||
let mut config_iter = configs.into_iter();
|
||||
let mut nodes_in_scope = Vec::new();
|
||||
let mut densely_chained = true;
|
||||
if chained {
|
||||
let Some(prev) = config_iter.next() else {
|
||||
return AddSystemsInnerResult {
|
||||
nodes: Vec::new(),
|
||||
densely_chained: true
|
||||
}
|
||||
};
|
||||
let mut previous_result = self.add_systems_inner(prev, true);
|
||||
densely_chained = previous_result.densely_chained;
|
||||
for current in config_iter {
|
||||
let current_result = self.add_systems_inner(current, true);
|
||||
densely_chained = densely_chained && current_result.densely_chained;
|
||||
match (
|
||||
previous_result.densely_chained,
|
||||
current_result.densely_chained,
|
||||
) {
|
||||
// Both groups are "densely" chained, so we can simplify the graph by only
|
||||
// chaining the last in the previous list to the first in the current list
|
||||
(true, true) => {
|
||||
let last_in_prev = previous_result.nodes.last().unwrap();
|
||||
let first_in_current = current_result.nodes.first().unwrap();
|
||||
self.dependency.graph.add_edge(
|
||||
*last_in_prev,
|
||||
*first_in_current,
|
||||
(),
|
||||
);
|
||||
}
|
||||
// The previous group is "densely" chained, so we can simplify the graph by only
|
||||
// chaining the last item from the previous list to every item in the current list
|
||||
(true, false) => {
|
||||
let last_in_prev = previous_result.nodes.last().unwrap();
|
||||
for current_node in ¤t_result.nodes {
|
||||
self.dependency.graph.add_edge(
|
||||
*last_in_prev,
|
||||
*current_node,
|
||||
(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// The current list is currently "densely" chained, so we can simplify the graph by
|
||||
// only chaining every item in the previous list to the first item in the current list
|
||||
(false, true) => {
|
||||
let first_in_current = current_result.nodes.first().unwrap();
|
||||
for previous_node in &previous_result.nodes {
|
||||
self.dependency.graph.add_edge(
|
||||
*previous_node,
|
||||
*first_in_current,
|
||||
(),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Neither of the lists are "densely" chained, so we must chain every item in the first
|
||||
// list to every item in the second list
|
||||
(false, false) => {
|
||||
for previous_node in &previous_result.nodes {
|
||||
for current_node in ¤t_result.nodes {
|
||||
self.dependency.graph.add_edge(
|
||||
*previous_node,
|
||||
*current_node,
|
||||
(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ancestor_chained {
|
||||
nodes_in_scope.append(&mut previous_result.nodes);
|
||||
}
|
||||
|
||||
previous_result = current_result;
|
||||
}
|
||||
|
||||
// ensure the last config's nodes are added
|
||||
if ancestor_chained {
|
||||
nodes_in_scope.append(&mut previous_result.nodes);
|
||||
}
|
||||
} else {
|
||||
let more_than_one_entry = config_iter.len() > 1;
|
||||
for config in config_iter {
|
||||
let result = self.add_systems_inner(config, ancestor_chained);
|
||||
densely_chained = densely_chained && result.densely_chained;
|
||||
if ancestor_chained {
|
||||
nodes_in_scope.extend(result.nodes);
|
||||
}
|
||||
}
|
||||
|
||||
// an "unchained" SystemConfig is only densely chained if it has exactly one densely chained entry
|
||||
if more_than_one_entry {
|
||||
densely_chained = false;
|
||||
}
|
||||
}
|
||||
|
||||
AddSystemsInnerResult {
|
||||
nodes: nodes_in_scope,
|
||||
densely_chained,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_system<M>(&mut self, system: impl IntoSystemConfig<M>) {
|
||||
self.add_system_inner(system).unwrap();
|
||||
}
|
||||
|
||||
fn add_system_inner<M>(
|
||||
&mut self,
|
||||
system: impl IntoSystemConfig<M>,
|
||||
) -> Result<NodeId, ScheduleBuildError> {
|
||||
let SystemConfig {
|
||||
system,
|
||||
graph_info,
|
||||
conditions,
|
||||
} = system.into_config();
|
||||
|
||||
fn add_system_inner(&mut self, config: SystemConfig) -> Result<NodeId, ScheduleBuildError> {
|
||||
let id = NodeId::System(self.systems.len());
|
||||
|
||||
// graph updates are immediate
|
||||
self.update_graphs(id, graph_info, false)?;
|
||||
self.update_graphs(id, config.graph_info)?;
|
||||
|
||||
// system init has to be deferred (need `&mut World`)
|
||||
self.uninit.push((id, 0));
|
||||
self.systems.push(SystemNode::new(system));
|
||||
self.system_conditions.push(Some(conditions));
|
||||
self.systems.push(SystemNode::new(config.system));
|
||||
self.system_conditions.push(Some(config.conditions));
|
||||
|
||||
Ok(id)
|
||||
}
|
||||
|
@ -639,7 +682,7 @@ impl ScheduleGraph {
|
|||
};
|
||||
|
||||
// graph updates are immediate
|
||||
self.update_graphs(id, graph_info, set.is_base())?;
|
||||
self.update_graphs(id, graph_info)?;
|
||||
|
||||
// system init has to be deferred (need `&mut World`)
|
||||
let system_set_conditions =
|
||||
|
@ -724,7 +767,6 @@ impl ScheduleGraph {
|
|||
&mut self,
|
||||
id: NodeId,
|
||||
graph_info: GraphInfo,
|
||||
is_base_set: bool,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
self.check_sets(&id, &graph_info)?;
|
||||
self.check_edges(&id, &graph_info)?;
|
||||
|
@ -734,8 +776,6 @@ impl ScheduleGraph {
|
|||
sets,
|
||||
dependencies,
|
||||
ambiguous_with,
|
||||
base_set,
|
||||
add_default_base_set,
|
||||
..
|
||||
} = graph_info;
|
||||
|
||||
|
@ -749,30 +789,6 @@ impl ScheduleGraph {
|
|||
self.dependency.graph.add_node(set);
|
||||
}
|
||||
|
||||
// If the current node is not a base set, set the base set if it was configured
|
||||
if !is_base_set {
|
||||
if let Some(base_set) = base_set {
|
||||
let set_id = self.system_set_ids[&base_set];
|
||||
self.hierarchy.graph.add_edge(set_id, id, ());
|
||||
} else if let Some(default_base_set) = &self.default_base_set {
|
||||
if add_default_base_set {
|
||||
match id {
|
||||
NodeId::System(_) => {
|
||||
// Queue the default base set. We queue systems instead of adding directly to allow
|
||||
// sets to define base sets, which will override the default inheritance behavior
|
||||
self.maybe_default_base_set.push(id);
|
||||
}
|
||||
NodeId::Set(_) => {
|
||||
// Sets should be added automatically because developers explicitly called
|
||||
// in_default_base_set()
|
||||
let set_id = self.system_set_ids[default_base_set];
|
||||
self.hierarchy.graph.add_edge(set_id, id, ());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.dependency.graph.contains_node(id) {
|
||||
self.dependency.graph.add_node(id);
|
||||
}
|
||||
|
@ -832,149 +848,15 @@ impl ScheduleGraph {
|
|||
}
|
||||
}
|
||||
|
||||
/// Calculates the base set for each node and caches the results on the node
|
||||
fn calculate_base_sets_and_detect_cycles(&mut self) -> Result<(), ScheduleBuildError> {
|
||||
let set_ids = (0..self.system_sets.len()).map(NodeId::Set);
|
||||
let system_ids = (0..self.systems.len()).map(NodeId::System);
|
||||
let mut visited_sets = vec![false; self.system_sets.len()];
|
||||
// reset base set membership, as this can change when the schedule updates
|
||||
for system in &mut self.systems {
|
||||
system.base_set_membership = BaseSetMembership::Uncalculated;
|
||||
}
|
||||
for system_set in &mut self.system_sets {
|
||||
system_set.base_set_membership = BaseSetMembership::Uncalculated;
|
||||
}
|
||||
for node_id in set_ids.chain(system_ids) {
|
||||
Self::calculate_base_set(
|
||||
&self.hierarchy,
|
||||
&mut self.system_sets,
|
||||
&mut self.systems,
|
||||
&mut visited_sets,
|
||||
node_id,
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_base_set(
|
||||
hierarchy: &Dag,
|
||||
system_sets: &mut [SystemSetNode],
|
||||
systems: &mut [SystemNode],
|
||||
visited_sets: &mut [bool],
|
||||
node_id: NodeId,
|
||||
) -> Result<Option<NodeId>, ScheduleBuildError> {
|
||||
let base_set_membership = match node_id {
|
||||
// systems only have
|
||||
NodeId::System(_) => BaseSetMembership::Uncalculated,
|
||||
NodeId::Set(index) => {
|
||||
let set_node = &mut system_sets[index];
|
||||
if set_node.inner.is_base() {
|
||||
set_node.base_set_membership = BaseSetMembership::Some(node_id);
|
||||
}
|
||||
set_node.base_set_membership
|
||||
}
|
||||
};
|
||||
let base_set = match base_set_membership {
|
||||
BaseSetMembership::None => None,
|
||||
BaseSetMembership::Some(node_id) => Some(node_id),
|
||||
BaseSetMembership::Uncalculated => {
|
||||
let mut base_set: Option<NodeId> = None;
|
||||
if let NodeId::Set(index) = node_id {
|
||||
if visited_sets[index] {
|
||||
return Err(ScheduleBuildError::HierarchyCycle);
|
||||
}
|
||||
visited_sets[index] = true;
|
||||
}
|
||||
for neighbor in hierarchy
|
||||
.graph
|
||||
.neighbors_directed(node_id, Direction::Incoming)
|
||||
{
|
||||
if let Some(calculated_base_set) = Self::calculate_base_set(
|
||||
hierarchy,
|
||||
system_sets,
|
||||
systems,
|
||||
visited_sets,
|
||||
neighbor,
|
||||
)? {
|
||||
if let Some(first_set) = base_set {
|
||||
if first_set != calculated_base_set {
|
||||
return Err(match node_id {
|
||||
NodeId::System(index) => {
|
||||
ScheduleBuildError::SystemInMultipleBaseSets {
|
||||
system: systems[index].name(),
|
||||
first_set: system_sets[first_set.index()].name(),
|
||||
second_set: system_sets[calculated_base_set.index()]
|
||||
.name(),
|
||||
}
|
||||
}
|
||||
NodeId::Set(index) => {
|
||||
ScheduleBuildError::SetInMultipleBaseSets {
|
||||
set: system_sets[index].name(),
|
||||
first_set: system_sets[first_set.index()].name(),
|
||||
second_set: system_sets[calculated_base_set.index()]
|
||||
.name(),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
base_set = Some(calculated_base_set);
|
||||
}
|
||||
}
|
||||
|
||||
match node_id {
|
||||
NodeId::System(index) => {
|
||||
systems[index].base_set_membership = if let Some(base_set) = base_set {
|
||||
BaseSetMembership::Some(base_set)
|
||||
} else {
|
||||
BaseSetMembership::None
|
||||
};
|
||||
}
|
||||
NodeId::Set(index) => {
|
||||
system_sets[index].base_set_membership = if let Some(base_set) = base_set {
|
||||
BaseSetMembership::Some(base_set)
|
||||
} else {
|
||||
BaseSetMembership::None
|
||||
};
|
||||
}
|
||||
}
|
||||
base_set
|
||||
}
|
||||
};
|
||||
Ok(base_set)
|
||||
}
|
||||
|
||||
/// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`].
|
||||
///
|
||||
/// This method also
|
||||
/// - calculates [`BaseSetMembership`]
|
||||
/// - checks for dependency or hierarchy cycles
|
||||
/// - checks for system access conflicts and reports ambiguities
|
||||
pub fn build_schedule(
|
||||
&mut self,
|
||||
components: &Components,
|
||||
) -> Result<SystemSchedule, ScheduleBuildError> {
|
||||
self.calculate_base_sets_and_detect_cycles()?;
|
||||
|
||||
// Add missing base set membership to systems that defaulted to using the
|
||||
// default base set and weren't added to a set that belongs to a base set.
|
||||
if let Some(default_base_set) = &self.default_base_set {
|
||||
let default_set_id = self.system_set_ids[default_base_set];
|
||||
for system_id in std::mem::take(&mut self.maybe_default_base_set) {
|
||||
let system_node = &mut self.systems[system_id.index()];
|
||||
if system_node.base_set_membership == BaseSetMembership::None {
|
||||
self.hierarchy.graph.add_edge(default_set_id, system_id, ());
|
||||
system_node.base_set_membership = BaseSetMembership::Some(default_set_id);
|
||||
}
|
||||
|
||||
debug_assert_ne!(
|
||||
system_node.base_set_membership,
|
||||
BaseSetMembership::Uncalculated,
|
||||
"base set membership should have been calculated"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// check hierarchy for cycles
|
||||
self.hierarchy.topsort = self
|
||||
.topsort_graph(&self.hierarchy.graph, ReportCycles::Hierarchy)
|
||||
|
@ -1138,18 +1020,18 @@ impl ScheduleGraph {
|
|||
ambiguous_with_flattened.add_edge(lhs, rhs, ());
|
||||
}
|
||||
(NodeId::Set(_), NodeId::System(_)) => {
|
||||
for &lhs_ in set_systems.get(&lhs).unwrap() {
|
||||
for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
|
||||
ambiguous_with_flattened.add_edge(lhs_, rhs, ());
|
||||
}
|
||||
}
|
||||
(NodeId::System(_), NodeId::Set(_)) => {
|
||||
for &rhs_ in set_systems.get(&rhs).unwrap() {
|
||||
for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
|
||||
ambiguous_with_flattened.add_edge(lhs, rhs_, ());
|
||||
}
|
||||
}
|
||||
(NodeId::Set(_), NodeId::Set(_)) => {
|
||||
for &lhs_ in set_systems.get(&lhs).unwrap() {
|
||||
for &rhs_ in set_systems.get(&rhs).unwrap() {
|
||||
for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
|
||||
for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
|
||||
ambiguous_with_flattened.add_edge(lhs_, rhs_, ());
|
||||
}
|
||||
}
|
||||
|
@ -1340,17 +1222,14 @@ impl ScheduleGraph {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn set_default_base_set(&mut self, set: Option<BoxedSystemSet>) {
|
||||
if let Some(set) = set {
|
||||
self.default_base_set = Some(set.dyn_clone());
|
||||
if self.system_set_ids.get(&set).is_none() {
|
||||
self.add_set(set);
|
||||
}
|
||||
} else {
|
||||
self.default_base_set = None;
|
||||
}
|
||||
}
|
||||
/// Values returned by `ScheduleGraph::add_systems_inner`
|
||||
struct AddSystemsInnerResult {
|
||||
/// All nodes contained inside this add_systems_inner call's SystemConfigs hierarchy
|
||||
nodes: Vec<NodeId>,
|
||||
/// True if and only if all nodes are "densely chained"
|
||||
densely_chained: bool,
|
||||
}
|
||||
|
||||
/// Used to select the appropriate reporting function.
|
||||
|
|
|
@ -23,34 +23,10 @@ pub trait SystemSet: DynHash + Debug + Send + Sync + 'static {
|
|||
None
|
||||
}
|
||||
|
||||
/// Returns `true` if this set is a "base system set". Systems
|
||||
/// can only belong to one base set at a time. Systems and Sets
|
||||
/// can only be added to base sets using specialized `in_base_set`
|
||||
/// APIs. This enables "mutually exclusive" behaviors. It also
|
||||
/// enables schedules to have a "default base set", which can be used
|
||||
/// to apply default configuration to systems.
|
||||
fn is_base(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Creates a boxed clone of the label corresponding to this system set.
|
||||
fn dyn_clone(&self) -> Box<dyn SystemSet>;
|
||||
}
|
||||
|
||||
/// A marker trait for `SystemSet` types where [`is_base`] returns `true`.
|
||||
/// This should only be implemented for types that satisfy this requirement.
|
||||
/// It is automatically implemented for base set types by `#[derive(SystemSet)]`.
|
||||
///
|
||||
/// [`is_base`]: SystemSet::is_base
|
||||
pub trait BaseSystemSet: SystemSet {}
|
||||
|
||||
/// A marker trait for `SystemSet` types where [`is_base`] returns `false`.
|
||||
/// This should only be implemented for types that satisfy this requirement.
|
||||
/// It is automatically implemented for non-base set types by `#[derive(SystemSet)]`.
|
||||
///
|
||||
/// [`is_base`]: SystemSet::is_base
|
||||
pub trait FreeSystemSet: SystemSet {}
|
||||
|
||||
impl PartialEq for dyn SystemSet {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.dyn_eq(other.as_dyn_eq())
|
||||
|
|
|
@ -54,6 +54,18 @@ pub struct OnEnter<S: States>(pub S);
|
|||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct OnExit<S: States>(pub S);
|
||||
|
||||
/// The label of a [`Schedule`](super::Schedule) that **only** runs whenever [`State<S>`]
|
||||
/// exits the `from` state, AND enters the `to` state.
|
||||
///
|
||||
/// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`].
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct OnTransition<S: States> {
|
||||
/// The state being exited.
|
||||
pub from: S,
|
||||
/// The state being entered.
|
||||
pub to: S,
|
||||
}
|
||||
|
||||
/// A [`SystemSet`] that will run within `CoreSet::Update` when this state is active.
|
||||
///
|
||||
/// This set, when created via `App::add_state`, is configured with both a base set and a run condition.
|
||||
|
@ -88,15 +100,18 @@ impl<S: States> NextState<S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Run the enter schedule for the current state
|
||||
/// Run the enter schedule (if it exists) for the current state.
|
||||
pub fn run_enter_schedule<S: States>(world: &mut World) {
|
||||
world.run_schedule(OnEnter(world.resource::<State<S>>().0.clone()));
|
||||
world
|
||||
.try_run_schedule(OnEnter(world.resource::<State<S>>().0.clone()))
|
||||
.ok();
|
||||
}
|
||||
|
||||
/// If a new state is queued in [`NextState<S>`], this system:
|
||||
/// - Takes the new state value from [`NextState<S>`] and updates [`State<S>`].
|
||||
/// - Runs the [`OnExit(exited_state)`] schedule.
|
||||
/// - Runs the [`OnEnter(entered_state)`] schedule.
|
||||
/// - Runs the [`OnExit(exited_state)`] schedule, if it exists.
|
||||
/// - Runs the [`OnTransition { from: exited_state, to: entered_state }`](OnTransition), if it exists.
|
||||
/// - Runs the [`OnEnter(entered_state)`] schedule, if it exists.
|
||||
pub fn apply_state_transition<S: States>(world: &mut World) {
|
||||
// We want to take the `NextState` resource,
|
||||
// but only mark it as changed if it wasn't empty.
|
||||
|
@ -105,7 +120,15 @@ pub fn apply_state_transition<S: States>(world: &mut World) {
|
|||
next_state_resource.set_changed();
|
||||
|
||||
let exited = mem::replace(&mut world.resource_mut::<State<S>>().0, entered.clone());
|
||||
world.run_schedule(OnExit(exited));
|
||||
world.run_schedule(OnEnter(entered));
|
||||
|
||||
// Try to run the schedules if they exist.
|
||||
world.try_run_schedule(OnExit(exited.clone())).ok();
|
||||
world
|
||||
.try_run_schedule(OnTransition {
|
||||
from: exited,
|
||||
to: entered.clone(),
|
||||
})
|
||||
.ok();
|
||||
world.try_run_schedule(OnEnter(entered)).ok();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,9 @@ use bevy_utils::OnDrop;
|
|||
|
||||
/// A flat, type-erased data storage type
|
||||
///
|
||||
/// Used to densely store homogeneous ECS data.
|
||||
/// Used to densely store homogeneous ECS data. A blob is usually just an arbitrary block of contiguous memory without any identity, and
|
||||
/// could be used to represent any arbitrary data (i.e. string, arrays, etc). This type is an extendable and reallcatable blob, which makes
|
||||
/// it a blobby Vec, a `BlobVec`.
|
||||
pub(super) struct BlobVec {
|
||||
item_layout: Layout,
|
||||
capacity: usize,
|
||||
|
@ -35,6 +37,13 @@ impl std::fmt::Debug for BlobVec {
|
|||
}
|
||||
|
||||
impl BlobVec {
|
||||
/// Creates a new [`BlobVec`] with the specified `capacity`.
|
||||
///
|
||||
/// `drop` is an optional function pointer that is meant to be invoked when any element in the [`BlobVec`]
|
||||
/// should be dropped. For all Rust-based types, this should match 1:1 with the implementation of [`Drop`]
|
||||
/// if present, and should be `None` if `T: !Drop`. For non-Rust based types, this should match any cleanup
|
||||
/// processes typically associated with the stored element.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been pushed into this [`BlobVec`].
|
||||
|
@ -42,6 +51,7 @@ impl BlobVec {
|
|||
/// If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`].
|
||||
///
|
||||
/// [`needs_drop`]: core::mem::needs_drop
|
||||
/// [`Drop`]: core::ops::Drop
|
||||
pub unsafe fn new(
|
||||
item_layout: Layout,
|
||||
drop: Option<unsafe fn(OwningPtr<'_>)>,
|
||||
|
@ -70,26 +80,36 @@ impl BlobVec {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the vector.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
/// Returns `true` if the vector contains no elements.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
/// Returns the total number of elements the vector can hold without reallocating.
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.capacity
|
||||
}
|
||||
|
||||
/// Returns the [`Layout`] of the element type stored in the vector.
|
||||
#[inline]
|
||||
pub fn layout(&self) -> Layout {
|
||||
self.item_layout
|
||||
}
|
||||
|
||||
/// Reserves the minimum capacity for at least `additional` more elements to be inserted in the given `BlobVec`.
|
||||
/// After calling `reserve_exact`, capacity will be greater than or equal to `self.len() + additional`. Does nothing if
|
||||
/// the capacity is already sufficient.
|
||||
///
|
||||
/// Note that the allocator may give the collection more space than it requests. Therefore, capacity can not be relied upon
|
||||
/// to be precisely minimal.
|
||||
pub fn reserve_exact(&mut self, additional: usize) {
|
||||
let available_space = self.capacity - self.len;
|
||||
if available_space < additional && self.item_layout.size() > 0 {
|
||||
|
@ -134,6 +154,8 @@ impl BlobVec {
|
|||
self.capacity = new_capacity;
|
||||
}
|
||||
|
||||
/// Initializes the value at `index` to `value`. This function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - index must be in bounds
|
||||
/// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this [`BlobVec`]'s
|
||||
|
@ -145,6 +167,8 @@ impl BlobVec {
|
|||
std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr.as_ptr(), self.item_layout.size());
|
||||
}
|
||||
|
||||
/// Replaces the value at `index` with `value`. This function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - index must be in-bounds
|
||||
/// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this
|
||||
|
@ -201,10 +225,10 @@ impl BlobVec {
|
|||
std::ptr::copy_nonoverlapping::<u8>(source, destination.as_ptr(), self.item_layout.size());
|
||||
}
|
||||
|
||||
/// Pushes a value to the [`BlobVec`].
|
||||
/// Appends an element to the back of the vector.
|
||||
///
|
||||
/// # Safety
|
||||
/// `value` must be valid to add to this [`BlobVec`]
|
||||
/// The `value` must match the [`layout`](`BlobVec::layout`) of the elements in the [`BlobVec`].
|
||||
#[inline]
|
||||
pub unsafe fn push(&mut self, value: OwningPtr<'_>) {
|
||||
self.reserve_exact(1);
|
||||
|
@ -213,6 +237,8 @@ impl BlobVec {
|
|||
self.initialize_unchecked(index, value);
|
||||
}
|
||||
|
||||
/// Forces the length of the vector to `len`.
|
||||
///
|
||||
/// # Safety
|
||||
/// `len` must be <= `capacity`. if length is decreased, "out of bounds" items must be dropped.
|
||||
/// Newly added items must be immediately populated with valid values and length must be
|
||||
|
@ -255,6 +281,7 @@ impl BlobVec {
|
|||
|
||||
/// Removes the value at `index` and copies the value stored into `ptr`.
|
||||
/// Does not do any bounds checking on `index`.
|
||||
/// The removed element is replaced by the last element of the `BlobVec`.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < `self.len()`
|
||||
|
@ -274,6 +301,10 @@ impl BlobVec {
|
|||
self.len -= 1;
|
||||
}
|
||||
|
||||
/// Removes the value at `index` and drops it.
|
||||
/// Does not do any bounds checking on `index`.
|
||||
/// The removed element is replaced by the last element of the `BlobVec`.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < self.len()
|
||||
#[inline]
|
||||
|
@ -286,8 +317,10 @@ impl BlobVec {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the element at `index`, without doing bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < self.len()
|
||||
/// It is the caller's responsibility to ensure that `index < self.len()`.
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked(&self, index: usize) -> Ptr<'_> {
|
||||
debug_assert!(index < self.len());
|
||||
|
@ -300,8 +333,10 @@ impl BlobVec {
|
|||
self.get_ptr().byte_add(index * size)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the element at `index`, without doing bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// It is the caller's responsibility to ensure that `index` is < self.len()
|
||||
/// It is the caller's responsibility to ensure that `index < self.len()`.
|
||||
#[inline]
|
||||
pub unsafe fn get_unchecked_mut(&mut self, index: usize) -> PtrMut<'_> {
|
||||
debug_assert!(index < self.len());
|
||||
|
@ -337,6 +372,9 @@ impl BlobVec {
|
|||
std::slice::from_raw_parts(self.data.as_ptr() as *const UnsafeCell<T>, self.len)
|
||||
}
|
||||
|
||||
/// Clears the vector, removing (and dropping) all values.
|
||||
///
|
||||
/// Note that this method has no effect on the allocated capacity of the vector.
|
||||
pub fn clear(&mut self) {
|
||||
let len = self.len;
|
||||
// We set len to 0 _before_ dropping elements for unwind safety. This ensures we don't
|
||||
|
|
|
@ -1,4 +1,24 @@
|
|||
//! Storage layouts for ECS data.
|
||||
//!
|
||||
//! This module implements the low-level collections that store data in a [`World`]. These all offer minimal and often
|
||||
//! unsafe APIs, and have been made `pub` primarily for debugging and monitoring purposes.
|
||||
//!
|
||||
//! # Fetching Storages
|
||||
//! Each of the below data stores can be fetched via [`Storages`], which can be fetched from a
|
||||
//! [`World`] via [`World::storages`]. It exposes a top level container for each class of storage within
|
||||
//! ECS:
|
||||
//!
|
||||
//! - [`Tables`] - columnar contiguous blocks of memory, optimized for fast iteration.
|
||||
//! - [`SparseSets`] - sparse `HashMap`-like mappings from entities to components, optimized for random
|
||||
//! lookup and regular insertion/removal of components.
|
||||
//! - [`Resources`] - singleton storage for the resources in the world
|
||||
//!
|
||||
//! # Safety
|
||||
//! To avoid trivially unsound use of the APIs in this module, it is explicitly impossible to get a mutable
|
||||
//! reference to [`Storages`] from [`World`], and none of the types publicly expose a mutable interface.
|
||||
//!
|
||||
//! [`World`]: crate::world::World
|
||||
//! [`World::storages`]: crate::world::World::storages
|
||||
|
||||
mod blob_vec;
|
||||
mod resource;
|
||||
|
@ -12,8 +32,12 @@ pub use table::*;
|
|||
/// The raw data stores of a [World](crate::world::World)
|
||||
#[derive(Default)]
|
||||
pub struct Storages {
|
||||
/// Backing storage for [`SparseSet`] components.
|
||||
pub sparse_sets: SparseSets,
|
||||
/// Backing storage for [`Table`] components.
|
||||
pub tables: Tables,
|
||||
/// Backing storage for resources.
|
||||
pub resources: Resources<true>,
|
||||
/// Backing storage for `!Send` resources.
|
||||
pub non_send_resources: Resources<false>,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::archetype::ArchetypeComponentId;
|
||||
use crate::change_detection::{MutUntyped, TicksMut};
|
||||
use crate::component::{ComponentId, ComponentTicks, Components, TickCells};
|
||||
use crate::component::{ComponentId, ComponentTicks, Components, Tick, TickCells};
|
||||
use crate::storage::{Column, SparseSet, TableRow};
|
||||
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
|
||||
use std::{mem::ManuallyDrop, thread::ThreadId};
|
||||
|
@ -42,6 +42,10 @@ impl<const SEND: bool> ResourceData<SEND> {
|
|||
/// The only row in the underlying column.
|
||||
const ROW: TableRow = TableRow::new(0);
|
||||
|
||||
/// Validates the access to `!Send` resources is only done on the thread they were created from.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from.
|
||||
#[inline]
|
||||
fn validate_access(&self) {
|
||||
if SEND {
|
||||
|
@ -70,7 +74,7 @@ impl<const SEND: bool> ResourceData<SEND> {
|
|||
self.id
|
||||
}
|
||||
|
||||
/// Gets a read-only pointer to the underlying resource, if available.
|
||||
/// Returns a reference to the resource, if it exists.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||
|
@ -83,12 +87,14 @@ impl<const SEND: bool> ResourceData<SEND> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
|
||||
/// Returns a reference to the resource's change ticks, if it exists.
|
||||
#[inline]
|
||||
pub fn get_ticks(&self) -> Option<ComponentTicks> {
|
||||
self.column.get_ticks(Self::ROW)
|
||||
}
|
||||
|
||||
/// Returns references to the resource and its change ticks, if it exists.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||
/// original thread it was inserted in.
|
||||
|
@ -100,17 +106,18 @@ impl<const SEND: bool> ResourceData<SEND> {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_mut(
|
||||
&mut self,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
) -> Option<MutUntyped<'_>> {
|
||||
/// Returns a mutable reference to the resource, if it exists.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `SEND` is false, this will panic if a value is present and is not accessed from the
|
||||
/// original thread it was inserted in.
|
||||
pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option<MutUntyped<'_>> {
|
||||
let (ptr, ticks) = self.get_with_ticks()?;
|
||||
Some(MutUntyped {
|
||||
// SAFETY: We have exclusive access to the underlying storage.
|
||||
value: unsafe { ptr.assert_unique() },
|
||||
// SAFETY: We have exclusive access to the underlying storage.
|
||||
ticks: unsafe { TicksMut::from_tick_cells(ticks, last_change_tick, change_tick) },
|
||||
ticks: unsafe { TicksMut::from_tick_cells(ticks, last_run, this_run) },
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -124,7 +131,7 @@ impl<const SEND: bool> ResourceData<SEND> {
|
|||
/// # Safety
|
||||
/// - `value` must be valid for the underlying type for the resource.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
|
||||
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: Tick) {
|
||||
if self.is_present() {
|
||||
self.validate_access();
|
||||
self.column.replace(Self::ROW, value, change_tick);
|
||||
|
@ -273,7 +280,7 @@ impl<const SEND: bool> Resources<SEND> {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for info in self.resources.values_mut() {
|
||||
info.column.check_change_ticks(change_tick);
|
||||
}
|
||||
|
|
|
@ -41,12 +41,16 @@ impl<I, V> SparseArray<I, V> {
|
|||
macro_rules! impl_sparse_array {
|
||||
($ty:ident) => {
|
||||
impl<I: SparseSetIndex, V> $ty<I, V> {
|
||||
/// Returns `true` if the collection contains a value for the specified `index`.
|
||||
#[inline]
|
||||
pub fn contains(&self, index: I) -> bool {
|
||||
let index = index.sparse_set_index();
|
||||
self.values.get(index).map(|v| v.is_some()).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Returns a reference to the value at `index`.
|
||||
///
|
||||
/// Returns `None` if `index` does not have a value or if `index` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get(&self, index: I) -> Option<&V> {
|
||||
let index = index.sparse_set_index();
|
||||
|
@ -60,6 +64,9 @@ impl_sparse_array!(SparseArray);
|
|||
impl_sparse_array!(ImmutableSparseArray);
|
||||
|
||||
impl<I: SparseSetIndex, V> SparseArray<I, V> {
|
||||
/// Inserts `value` at `index` in the array.
|
||||
///
|
||||
/// If `index` is out-of-bounds, this will enlarge the buffer to accommodate it.
|
||||
#[inline]
|
||||
pub fn insert(&mut self, index: I, value: V) {
|
||||
let index = index.sparse_set_index();
|
||||
|
@ -69,6 +76,9 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
|
|||
self.values[index] = Some(value);
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the value at `index`.
|
||||
///
|
||||
/// Returns `None` if `index` does not have a value or if `index` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
|
||||
let index = index.sparse_set_index();
|
||||
|
@ -78,16 +88,21 @@ impl<I: SparseSetIndex, V> SparseArray<I, V> {
|
|||
.unwrap_or(None)
|
||||
}
|
||||
|
||||
/// Removes and returns the value stored at `index`.
|
||||
///
|
||||
/// Returns `None` if `index` did not have a value or if `index` is out of bounds.
|
||||
#[inline]
|
||||
pub fn remove(&mut self, index: I) -> Option<V> {
|
||||
let index = index.sparse_set_index();
|
||||
self.values.get_mut(index).and_then(|value| value.take())
|
||||
}
|
||||
|
||||
/// Removes all of the values stored within.
|
||||
pub fn clear(&mut self) {
|
||||
self.values.clear();
|
||||
}
|
||||
|
||||
/// Converts the [`SparseArray`] into an immutable variant.
|
||||
pub(crate) fn into_immutable(self) -> ImmutableSparseArray<I, V> {
|
||||
ImmutableSparseArray {
|
||||
values: self.values.into_boxed_slice(),
|
||||
|
@ -113,6 +128,8 @@ pub struct ComponentSparseSet {
|
|||
}
|
||||
|
||||
impl ComponentSparseSet {
|
||||
/// Creates a new [`ComponentSparseSet`] with a given component type layout and
|
||||
/// initial `capacity`.
|
||||
pub(crate) fn new(component_info: &ComponentInfo, capacity: usize) -> Self {
|
||||
Self {
|
||||
dense: Column::with_capacity(component_info, capacity),
|
||||
|
@ -121,17 +138,20 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Removes all of the values stored within.
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.dense.clear();
|
||||
self.entities.clear();
|
||||
self.sparse.clear();
|
||||
}
|
||||
|
||||
/// Returns the number of component values in the sparse set.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.dense.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the sparse set contains no component values.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dense.len() == 0
|
||||
|
@ -143,7 +163,12 @@ impl ComponentSparseSet {
|
|||
/// # Safety
|
||||
/// The `value` pointer must point to a valid address that matches the [`Layout`](std::alloc::Layout)
|
||||
/// inside the [`ComponentInfo`] given when constructing this sparse set.
|
||||
pub(crate) unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u32) {
|
||||
pub(crate) unsafe fn insert(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
value: OwningPtr<'_>,
|
||||
change_tick: Tick,
|
||||
) {
|
||||
if let Some(&dense_index) = self.sparse.get(entity.index()) {
|
||||
#[cfg(debug_assertions)]
|
||||
assert_eq!(entity, self.entities[dense_index as usize]);
|
||||
|
@ -162,6 +187,7 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the sparse set has a component value for the provided `entity`.
|
||||
#[inline]
|
||||
pub fn contains(&self, entity: Entity) -> bool {
|
||||
#[cfg(debug_assertions)]
|
||||
|
@ -178,6 +204,9 @@ impl ComponentSparseSet {
|
|||
self.sparse.contains(entity.index())
|
||||
}
|
||||
|
||||
/// Returns a reference to the entity's component value.
|
||||
///
|
||||
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||
#[inline]
|
||||
pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> {
|
||||
self.sparse.get(entity.index()).map(|dense_index| {
|
||||
|
@ -189,6 +218,9 @@ impl ComponentSparseSet {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns references to the entity's component value and its added and changed ticks.
|
||||
///
|
||||
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||
#[inline]
|
||||
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
||||
let dense_index = TableRow::new(*self.sparse.get(entity.index())? as usize);
|
||||
|
@ -206,6 +238,9 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the "added" tick of the entity's component value.
|
||||
///
|
||||
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||
#[inline]
|
||||
pub fn get_added_ticks(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
|
||||
let dense_index = *self.sparse.get(entity.index())? as usize;
|
||||
|
@ -220,6 +255,9 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the "changed" tick of the entity's component value.
|
||||
///
|
||||
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||
#[inline]
|
||||
pub fn get_changed_ticks(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
|
||||
let dense_index = *self.sparse.get(entity.index())? as usize;
|
||||
|
@ -234,6 +272,9 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the "added" and "changed" ticks of the entity's component value.
|
||||
///
|
||||
/// Returns `None` if `entity` does not have a component in the sparse set.
|
||||
#[inline]
|
||||
pub fn get_ticks(&self, entity: Entity) -> Option<ComponentTicks> {
|
||||
let dense_index = *self.sparse.get(entity.index())? as usize;
|
||||
|
@ -270,6 +311,9 @@ impl ComponentSparseSet {
|
|||
})
|
||||
}
|
||||
|
||||
/// Removes (and drops) the entity's component value from the sparse set.
|
||||
///
|
||||
/// Returns `true` if `entity` had a component value in the sparse set.
|
||||
pub(crate) fn remove(&mut self, entity: Entity) -> bool {
|
||||
if let Some(dense_index) = self.sparse.remove(entity.index()) {
|
||||
let dense_index = dense_index as usize;
|
||||
|
@ -293,7 +337,7 @@ impl ComponentSparseSet {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
self.dense.check_change_ticks(change_tick);
|
||||
}
|
||||
}
|
||||
|
@ -320,16 +364,21 @@ pub(crate) struct ImmutableSparseSet<I, V: 'static> {
|
|||
macro_rules! impl_sparse_set {
|
||||
($ty:ident) => {
|
||||
impl<I: SparseSetIndex, V> $ty<I, V> {
|
||||
/// Returns the number of elements in the sparse set.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.dense.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the sparse set contains a value for `index`.
|
||||
#[inline]
|
||||
pub fn contains(&self, index: I) -> bool {
|
||||
self.sparse.contains(index)
|
||||
}
|
||||
|
||||
/// Returns a reference to the value for `index`.
|
||||
///
|
||||
/// Returns `None` if `index` does not have a value in the sparse set.
|
||||
pub fn get(&self, index: I) -> Option<&V> {
|
||||
self.sparse.get(index).map(|dense_index| {
|
||||
// SAFETY: if the sparse index points to something in the dense vec, it exists
|
||||
|
@ -337,6 +386,9 @@ macro_rules! impl_sparse_set {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the value for `index`.
|
||||
///
|
||||
/// Returns `None` if `index` does not have a value in the sparse set.
|
||||
pub fn get_mut(&mut self, index: I) -> Option<&mut V> {
|
||||
let dense = &mut self.dense;
|
||||
self.sparse.get(index).map(move |dense_index| {
|
||||
|
@ -345,22 +397,27 @@ macro_rules! impl_sparse_set {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all keys (indices) in arbitrary order.
|
||||
pub fn indices(&self) -> impl Iterator<Item = I> + '_ {
|
||||
self.indices.iter().cloned()
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all values in arbitrary order.
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
self.dense.iter()
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all values mutably in arbitrary order.
|
||||
pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
|
||||
self.dense.iter_mut()
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all key-value pairs in arbitrary order, with references to the values.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&I, &V)> {
|
||||
self.indices.iter().zip(self.dense.iter())
|
||||
}
|
||||
|
||||
/// Returns an iterator visiting all key-value pairs in arbitrary order, with mutable references to the values.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&I, &mut V)> {
|
||||
self.indices.iter().zip(self.dense.iter_mut())
|
||||
}
|
||||
|
@ -378,6 +435,7 @@ impl<I: SparseSetIndex, V> Default for SparseSet<I, V> {
|
|||
}
|
||||
|
||||
impl<I, V> SparseSet<I, V> {
|
||||
/// Creates a new [`SparseSet`].
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
dense: Vec::new(),
|
||||
|
@ -388,6 +446,7 @@ impl<I, V> SparseSet<I, V> {
|
|||
}
|
||||
|
||||
impl<I: SparseSetIndex, V> SparseSet<I, V> {
|
||||
/// Creates a new [`SparseSet`] with a specified initial capacity.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
dense: Vec::with_capacity(capacity),
|
||||
|
@ -396,11 +455,15 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns the total number of elements the [`SparseSet`] can hold without needing to reallocate.
|
||||
#[inline]
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.dense.capacity()
|
||||
}
|
||||
|
||||
/// Inserts `value` at `index`.
|
||||
///
|
||||
/// If a value was already present at `index`, it will be overwritten.
|
||||
pub fn insert(&mut self, index: I, value: V) {
|
||||
if let Some(dense_index) = self.sparse.get(index.clone()).cloned() {
|
||||
// SAFETY: dense indices stored in self.sparse always exist
|
||||
|
@ -414,6 +477,8 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a reference to the value for `index`, inserting one computed from `func`
|
||||
/// if not already present.
|
||||
pub fn get_or_insert_with(&mut self, index: I, func: impl FnOnce() -> V) -> &mut V {
|
||||
if let Some(dense_index) = self.sparse.get(index.clone()).cloned() {
|
||||
// SAFETY: dense indices stored in self.sparse always exist
|
||||
|
@ -429,11 +494,15 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the sparse set contains no elements.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.dense.len() == 0
|
||||
}
|
||||
|
||||
/// Removes and returns the value for `index`.
|
||||
///
|
||||
/// Returns `None` if `index` does not have a value in the sparse set.
|
||||
pub fn remove(&mut self, index: I) -> Option<V> {
|
||||
self.sparse.remove(index).map(|dense_index| {
|
||||
let is_last = dense_index == self.dense.len() - 1;
|
||||
|
@ -447,12 +516,14 @@ impl<I: SparseSetIndex, V> SparseSet<I, V> {
|
|||
})
|
||||
}
|
||||
|
||||
/// Clears all of the elements from the sparse set.
|
||||
pub fn clear(&mut self) {
|
||||
self.dense.clear();
|
||||
self.indices.clear();
|
||||
self.sparse.clear();
|
||||
}
|
||||
|
||||
/// Converts the sparse set into its immutable variant.
|
||||
pub(crate) fn into_immutable(self) -> ImmutableSparseSet<I, V> {
|
||||
ImmutableSparseSet {
|
||||
dense: self.dense.into_boxed_slice(),
|
||||
|
@ -548,7 +619,7 @@ impl SparseSets {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for set in self.sets.values_mut() {
|
||||
set.check_change_ticks(change_tick);
|
||||
}
|
||||
|
|
|
@ -33,16 +33,24 @@ pub struct TableId(u32);
|
|||
impl TableId {
|
||||
pub(crate) const INVALID: TableId = TableId(u32::MAX);
|
||||
|
||||
/// Creates a new [`TableId`].
|
||||
///
|
||||
/// `index` *must* be retrieved from calling [`TableId::index`] on a `TableId` you got
|
||||
/// from a table of a given [`World`] or the created ID may be invalid.
|
||||
///
|
||||
/// [`World`]: crate::world::World
|
||||
#[inline]
|
||||
pub fn new(index: usize) -> Self {
|
||||
TableId(index as u32)
|
||||
}
|
||||
|
||||
/// Gets the underlying table index from the ID.
|
||||
#[inline]
|
||||
pub fn index(self) -> usize {
|
||||
self.0 as usize
|
||||
}
|
||||
|
||||
/// The [`TableId`] of the [`Table`] without any components.
|
||||
#[inline]
|
||||
pub const fn empty() -> TableId {
|
||||
TableId(0)
|
||||
|
@ -71,7 +79,7 @@ impl TableId {
|
|||
pub struct TableRow(u32);
|
||||
|
||||
impl TableRow {
|
||||
pub const INVALID: TableRow = TableRow(u32::MAX);
|
||||
pub(crate) const INVALID: TableRow = TableRow(u32::MAX);
|
||||
|
||||
/// Creates a `TableRow`.
|
||||
#[inline]
|
||||
|
@ -86,6 +94,19 @@ impl TableRow {
|
|||
}
|
||||
}
|
||||
|
||||
/// A type-erased contiguous container for data of a homogenous type.
|
||||
///
|
||||
/// Conceptually, a [`Column`] is very similar to a type-erased `Vec<T>`.
|
||||
/// It also stores the change detection ticks for its components, kept in two separate
|
||||
/// contiguous buffers internally. An element shares its data across these buffers by using the
|
||||
/// same index (i.e. the entity at row 3 has it's data at index 3 and its change detection ticks at
|
||||
/// index 3). A slice to these contiguous blocks of memory can be fetched
|
||||
/// via [`Column::get_data_slice`], [`Column::get_added_ticks_slice`], and
|
||||
/// [`Column::get_changed_ticks_slice`].
|
||||
///
|
||||
/// Like many other low-level storage types, [`Column`] has a limited and highly unsafe
|
||||
/// interface. It's highly advised to use higher level types and their safe abstractions
|
||||
/// instead of working directly with [`Column`].
|
||||
#[derive(Debug)]
|
||||
pub struct Column {
|
||||
data: BlobVec,
|
||||
|
@ -94,6 +115,7 @@ pub struct Column {
|
|||
}
|
||||
|
||||
impl Column {
|
||||
/// Constructs a new [`Column`], configured with a component's layout and an initial `capacity`.
|
||||
#[inline]
|
||||
pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self {
|
||||
Column {
|
||||
|
@ -104,6 +126,7 @@ impl Column {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fetches the [`Layout`] for the underlying type.
|
||||
#[inline]
|
||||
pub fn item_layout(&self) -> Layout {
|
||||
self.data.layout()
|
||||
|
@ -129,13 +152,10 @@ impl Column {
|
|||
/// # Safety
|
||||
/// Assumes data has already been allocated for the given row.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: u32) {
|
||||
pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: Tick) {
|
||||
debug_assert!(row.index() < self.len());
|
||||
self.data.replace_unchecked(row.index(), data);
|
||||
self.changed_ticks
|
||||
.get_unchecked_mut(row.index())
|
||||
.get_mut()
|
||||
.set_changed(change_tick);
|
||||
*self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = change_tick;
|
||||
}
|
||||
|
||||
/// Writes component data to the column at given row.
|
||||
|
@ -150,18 +170,29 @@ impl Column {
|
|||
self.data.replace_unchecked(row.index(), data);
|
||||
}
|
||||
|
||||
/// Gets the current number of elements stored in the column.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
/// Checks if the column is empty. Returns `true` if there are no elements, `false` otherwise.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
/// Removes an element from the [`Column`].
|
||||
///
|
||||
/// - The value will be dropped if it implements [`Drop`].
|
||||
/// - This does not preserve ordering, but is O(1).
|
||||
/// - This does not do any bounds checking.
|
||||
/// - The element is replaced with the last element in the [`Column`].
|
||||
///
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
///
|
||||
/// [`Drop`]: std::ops::Drop
|
||||
#[inline]
|
||||
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) {
|
||||
self.data.swap_remove_and_drop_unchecked(row.index());
|
||||
|
@ -169,6 +200,16 @@ impl Column {
|
|||
self.changed_ticks.swap_remove(row.index());
|
||||
}
|
||||
|
||||
/// Removes an element from the [`Column`] and returns it and its change detection ticks.
|
||||
/// This does not preserve ordering, but is O(1).
|
||||
///
|
||||
/// The element is replaced with the last element in the [`Column`].
|
||||
///
|
||||
/// It is the caller's responsibility to ensure that the removed value is dropped or used.
|
||||
/// Failure to do so may result in resources not being released (i.e. files handles not being
|
||||
/// released, memory leaks, etc.)
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to drop the removed component"]
|
||||
pub(crate) fn swap_remove_and_forget(
|
||||
|
@ -184,8 +225,18 @@ impl Column {
|
|||
})
|
||||
}
|
||||
|
||||
/// Removes an element from the [`Column`] and returns it and its change detection ticks.
|
||||
/// This does not preserve ordering, but is O(1). Unlike [`Column::swap_remove_and_forget`]
|
||||
/// this does not do any bounds checking.
|
||||
///
|
||||
/// The element is replaced with the last element in the [`Column`].
|
||||
///
|
||||
/// It's the caller's responsibility to ensure that the removed value is dropped or used.
|
||||
/// Failure to do so may result in resources not being released (i.e. files handles not being
|
||||
/// released, memory leaks, etc.)
|
||||
///
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
#[must_use = "The returned pointer should be used to dropped the removed component"]
|
||||
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
|
||||
|
@ -225,8 +276,10 @@ impl Column {
|
|||
other.changed_ticks.swap_remove(src_row.index());
|
||||
}
|
||||
|
||||
// # Safety
|
||||
// - ptr must point to valid data of this column's component type
|
||||
/// Pushes a new value onto the end of the [`Column`].
|
||||
///
|
||||
/// # Safety
|
||||
/// `ptr` must point to valid data of this column's component type
|
||||
pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) {
|
||||
self.data.push(ptr);
|
||||
self.added_ticks.push(UnsafeCell::new(ticks.added));
|
||||
|
@ -240,27 +293,57 @@ impl Column {
|
|||
self.changed_ticks.reserve_exact(additional);
|
||||
}
|
||||
|
||||
/// Fetches the data pointer to the first element of the [`Column`].
|
||||
///
|
||||
/// The pointer is type erased, so using this function to fetch anything
|
||||
/// other than the first element will require computing the offset using
|
||||
/// [`Column::item_layout`].
|
||||
#[inline]
|
||||
pub fn get_data_ptr(&self) -> Ptr<'_> {
|
||||
self.data.get_ptr()
|
||||
}
|
||||
|
||||
/// Fetches the slice to the [`Column`]'s data cast to a given type.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
///
|
||||
/// # Safety
|
||||
/// The type `T` must be the type of the items in this column.
|
||||
///
|
||||
/// [`UnsafeCell`]: std::cell::UnsafeCell
|
||||
pub unsafe fn get_data_slice<T>(&self) -> &[UnsafeCell<T>] {
|
||||
self.data.get_slice()
|
||||
}
|
||||
|
||||
/// Fetches the slice to the [`Column`]'s "added" change detection ticks.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
///
|
||||
/// [`UnsafeCell`]: std::cell::UnsafeCell
|
||||
#[inline]
|
||||
pub fn get_added_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
|
||||
&self.added_ticks
|
||||
}
|
||||
|
||||
/// Fetches the slice to the [`Column`]'s "changed" change detection ticks.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
///
|
||||
/// [`UnsafeCell`]: std::cell::UnsafeCell
|
||||
#[inline]
|
||||
pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
|
||||
&self.changed_ticks
|
||||
}
|
||||
|
||||
/// Fetches a reference to the data and change detection ticks at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> {
|
||||
(row.index() < self.data.len())
|
||||
|
@ -277,6 +360,9 @@ impl Column {
|
|||
})
|
||||
}
|
||||
|
||||
/// Fetches a read-only reference to the data at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_data(&self, row: TableRow) -> Option<Ptr<'_>> {
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
|
@ -284,15 +370,21 @@ impl Column {
|
|||
(row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked(row.index()) })
|
||||
}
|
||||
|
||||
/// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not
|
||||
/// do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - index must be in-bounds
|
||||
/// - no other reference to the data of the same row can exist at the same time
|
||||
/// - `row` must be within the range `[0, self.len())`.
|
||||
/// - no other mutable reference to the data of the same row can exist at the same time
|
||||
#[inline]
|
||||
pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> {
|
||||
debug_assert!(row.index() < self.data.len());
|
||||
self.data.get_unchecked(row.index())
|
||||
}
|
||||
|
||||
/// Fetches a mutable reference to the data at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_data_mut(&mut self, row: TableRow) -> Option<PtrMut<'_>> {
|
||||
// SAFETY: The row is length checked before fetching the pointer. This is being
|
||||
|
@ -300,6 +392,9 @@ impl Column {
|
|||
(row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row.index()) })
|
||||
}
|
||||
|
||||
/// Fetches a mutable reference to the data at `row`. Unlike [`Column::get_data_mut`] this does not
|
||||
/// do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// - index must be in-bounds
|
||||
/// - no other reference to the data of the same row can exist at the same time
|
||||
|
@ -309,16 +404,37 @@ impl Column {
|
|||
self.data.get_unchecked_mut(row.index())
|
||||
}
|
||||
|
||||
/// Fetches the "added" change detection ticks for the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
///
|
||||
/// [`UnsafeCell`]: std::cell::UnsafeCell
|
||||
#[inline]
|
||||
pub fn get_added_ticks(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
|
||||
self.added_ticks.get(row.index())
|
||||
}
|
||||
|
||||
/// Fetches the "changed" change detection ticks for the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
///
|
||||
/// Note: The values stored within are [`UnsafeCell`].
|
||||
/// Users of this API must ensure that accesses to each individual element
|
||||
/// adhere to the safety invariants of [`UnsafeCell`].
|
||||
///
|
||||
/// [`UnsafeCell`]: std::cell::UnsafeCell
|
||||
#[inline]
|
||||
pub fn get_changed_ticks(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
|
||||
self.changed_ticks.get(row.index())
|
||||
}
|
||||
|
||||
/// Fetches the change detection ticks for the value at `row`.
|
||||
///
|
||||
/// Returns `None` if `row` is out of bounds.
|
||||
#[inline]
|
||||
pub fn get_ticks(&self, row: TableRow) -> Option<ComponentTicks> {
|
||||
if row.index() < self.data.len() {
|
||||
|
@ -329,24 +445,33 @@ impl Column {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fetches the "added" change detection ticks for the value at `row`. Unlike [`Column::get_added_ticks`]
|
||||
/// this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
pub unsafe fn get_added_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
|
||||
debug_assert!(row.index() < self.added_ticks.len());
|
||||
self.added_ticks.get_unchecked(row.index())
|
||||
}
|
||||
|
||||
/// Fetches the "changed" change detection ticks for the value at `row`. Unlike [`Column::get_changed_ticks`]
|
||||
/// this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
pub unsafe fn get_changed_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
|
||||
debug_assert!(row.index() < self.changed_ticks.len());
|
||||
self.changed_ticks.get_unchecked(row.index())
|
||||
}
|
||||
|
||||
/// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`]
|
||||
/// this function does not do any bounds checking.
|
||||
///
|
||||
/// # Safety
|
||||
/// index must be in-bounds
|
||||
/// `row` must be within the range `[0, self.len())`.
|
||||
#[inline]
|
||||
pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks {
|
||||
debug_assert!(row.index() < self.added_ticks.len());
|
||||
|
@ -357,6 +482,9 @@ impl Column {
|
|||
}
|
||||
}
|
||||
|
||||
/// Clears the column, removing all values.
|
||||
///
|
||||
/// Note that this function has no effect on the allocated capacity of the [`Column`]>
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
self.added_ticks.clear();
|
||||
|
@ -364,7 +492,7 @@ impl Column {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for component_ticks in &mut self.added_ticks {
|
||||
component_ticks.get_mut().check_tick(change_tick);
|
||||
}
|
||||
|
@ -417,10 +545,10 @@ impl TableBuilder {
|
|||
/// in a [`World`].
|
||||
///
|
||||
/// Conceptually, a `Table` can be thought of as an `HashMap<ComponentId, Column>`, where
|
||||
/// each `Column` is a type-erased `Vec<T: Component>`. Each row corresponds to a single entity
|
||||
/// each [`Column`] is a type-erased `Vec<T: Component>`. Each row corresponds to a single entity
|
||||
/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same
|
||||
/// entity). Fetching components from a table involves fetching the associated column for a
|
||||
/// component type (via it's [`ComponentId`]), then fetching the entity's row within that column.
|
||||
/// component type (via its [`ComponentId`]), then fetching the entity's row within that column.
|
||||
///
|
||||
/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays
|
||||
/// [`Component`]: crate::component::Component
|
||||
|
@ -431,6 +559,7 @@ pub struct Table {
|
|||
}
|
||||
|
||||
impl Table {
|
||||
/// Fetches a read-only slice of the entities stored within the [`Table`].
|
||||
#[inline]
|
||||
pub fn entities(&self) -> &[Entity] {
|
||||
&self.entities
|
||||
|
@ -457,7 +586,8 @@ impl Table {
|
|||
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
||||
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
||||
/// to replace it (if an entity was swapped in). missing columns will be "forgotten". It is
|
||||
/// the caller's responsibility to drop them
|
||||
/// the caller's responsibility to drop them. Failure to do so may result in resources not
|
||||
/// being released (i.e. files handles not being released, memory leaks, etc.)
|
||||
///
|
||||
/// # Safety
|
||||
/// Row must be in-bounds
|
||||
|
@ -548,21 +678,39 @@ impl Table {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fetches a read-only reference to the [`Column`] for a given [`Component`] within the
|
||||
/// table.
|
||||
///
|
||||
/// Returns `None` if the corresponding component does not belong to the table.
|
||||
///
|
||||
/// [`Component`]: crate::component::Component
|
||||
#[inline]
|
||||
pub fn get_column(&self, component_id: ComponentId) -> Option<&Column> {
|
||||
self.columns.get(component_id)
|
||||
}
|
||||
|
||||
/// Fetches a mutable reference to the [`Column`] for a given [`Component`] within the
|
||||
/// table.
|
||||
///
|
||||
/// Returns `None` if the corresponding component does not belong to the table.
|
||||
///
|
||||
/// [`Component`]: crate::component::Component
|
||||
#[inline]
|
||||
pub(crate) fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut Column> {
|
||||
self.columns.get_mut(component_id)
|
||||
}
|
||||
|
||||
/// Checks if the table contains a [`Column`] for a given [`Component`].
|
||||
///
|
||||
/// Returns `true` if the column is present, `false` otherwise.
|
||||
///
|
||||
/// [`Component`]: crate::component::Component
|
||||
#[inline]
|
||||
pub fn has_column(&self, component_id: ComponentId) -> bool {
|
||||
self.columns.contains(component_id)
|
||||
}
|
||||
|
||||
/// Reserves `additional` elements worth of capacity within the table.
|
||||
pub(crate) fn reserve(&mut self, additional: usize) {
|
||||
if self.entities.capacity() - self.entities.len() < additional {
|
||||
self.entities.reserve(additional);
|
||||
|
@ -592,36 +740,45 @@ impl Table {
|
|||
TableRow::new(index)
|
||||
}
|
||||
|
||||
/// Gets the number of entities currently being stored in the table.
|
||||
#[inline]
|
||||
pub fn entity_count(&self) -> usize {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
/// Gets the number of components being stored in the table.
|
||||
#[inline]
|
||||
pub fn component_count(&self) -> usize {
|
||||
self.columns.len()
|
||||
}
|
||||
|
||||
/// Gets the maximum number of entities the table can currently store
|
||||
/// without reallocating the underlying memory.
|
||||
#[inline]
|
||||
pub fn entity_capacity(&self) -> usize {
|
||||
self.entities.capacity()
|
||||
}
|
||||
|
||||
/// Checks if the [`Table`] is empty or not.
|
||||
///
|
||||
/// Returns `true` if the table contains no entities, `false` otherwise.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entities.is_empty()
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for column in self.columns.values_mut() {
|
||||
column.check_change_ticks(change_tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterates over the [`Column`]s of the [`Table`].
|
||||
pub fn iter(&self) -> impl Iterator<Item = &Column> {
|
||||
self.columns.values()
|
||||
}
|
||||
|
||||
/// Clears all of the stored components in the [`Table`].
|
||||
pub(crate) fn clear(&mut self) {
|
||||
self.entities.clear();
|
||||
for column in self.columns.values_mut() {
|
||||
|
@ -666,11 +823,19 @@ impl Tables {
|
|||
self.tables.is_empty()
|
||||
}
|
||||
|
||||
/// Fetches a [`Table`] by its [`TableId`].
|
||||
///
|
||||
/// Returns `None` if `id` is invalid.
|
||||
#[inline]
|
||||
pub fn get(&self, id: TableId) -> Option<&Table> {
|
||||
self.tables.get(id.index())
|
||||
}
|
||||
|
||||
/// Fetches mutable references to two different [`Table`]s.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `a` and `b` are equal.
|
||||
#[inline]
|
||||
pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) {
|
||||
if a.index() > b.index() {
|
||||
|
@ -682,6 +847,9 @@ impl Tables {
|
|||
}
|
||||
}
|
||||
|
||||
/// Attempts to fetch a table based on the provided components,
|
||||
/// creating and returning a new [`Table`] if one did not already exist.
|
||||
///
|
||||
/// # Safety
|
||||
/// `component_ids` must contain components that exist in `components`
|
||||
pub(crate) unsafe fn get_id_or_insert(
|
||||
|
@ -706,17 +874,19 @@ impl Tables {
|
|||
*value
|
||||
}
|
||||
|
||||
/// Iterates through all of the tables stored within in [`TableId`] order.
|
||||
pub fn iter(&self) -> std::slice::Iter<'_, Table> {
|
||||
self.tables.iter()
|
||||
}
|
||||
|
||||
/// Clears all data from all [`Table`]s stored within.
|
||||
pub(crate) fn clear(&mut self) {
|
||||
for table in &mut self.tables {
|
||||
table.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for table in &mut self.tables {
|
||||
table.check_change_ticks(change_tick);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@ use std::{borrow::Cow, cell::UnsafeCell, marker::PhantomData};
|
|||
use bevy_ptr::UnsafeCellDeref;
|
||||
|
||||
use crate::{
|
||||
archetype::ArchetypeComponentId, component::ComponentId, prelude::World, query::Access,
|
||||
archetype::ArchetypeComponentId,
|
||||
component::{ComponentId, Tick},
|
||||
prelude::World,
|
||||
query::Access,
|
||||
};
|
||||
|
||||
use super::{ReadOnlySystem, System};
|
||||
|
@ -47,7 +50,7 @@ use super::{ReadOnlySystem, System};
|
|||
/// # world.init_resource::<RanFlag>();
|
||||
/// #
|
||||
/// # let mut app = Schedule::new();
|
||||
/// app.add_system(my_system.run_if(Xor::new(
|
||||
/// app.add_systems(my_system.run_if(Xor::new(
|
||||
/// IntoSystem::into_system(resource_equals(A(1))),
|
||||
/// IntoSystem::into_system(resource_equals(B(1))),
|
||||
/// // The name of the combined system.
|
||||
|
@ -203,18 +206,18 @@ where
|
|||
.extend(self.b.archetype_component_access());
|
||||
}
|
||||
|
||||
fn check_change_tick(&mut self, change_tick: u32) {
|
||||
fn check_change_tick(&mut self, change_tick: Tick) {
|
||||
self.a.check_change_tick(change_tick);
|
||||
self.b.check_change_tick(change_tick);
|
||||
}
|
||||
|
||||
fn get_last_change_tick(&self) -> u32 {
|
||||
self.a.get_last_change_tick()
|
||||
fn get_last_run(&self) -> Tick {
|
||||
self.a.get_last_run()
|
||||
}
|
||||
|
||||
fn set_last_change_tick(&mut self, last_change_tick: u32) {
|
||||
self.a.set_last_change_tick(last_change_tick);
|
||||
self.b.set_last_change_tick(last_change_tick);
|
||||
fn set_last_run(&mut self, last_run: Tick) {
|
||||
self.a.set_last_run(last_run);
|
||||
self.b.set_last_run(last_run);
|
||||
}
|
||||
|
||||
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule::SystemSet>> {
|
||||
|
|
|
@ -582,9 +582,9 @@ impl<'w, 's> Commands<'w, 's> {
|
|||
/// # world.init_resource::<Counter>();
|
||||
/// #
|
||||
/// # let mut setup_schedule = Schedule::new();
|
||||
/// # setup_schedule.add_system(setup);
|
||||
/// # setup_schedule.add_systems(setup);
|
||||
/// # let mut assert_schedule = Schedule::new();
|
||||
/// # assert_schedule.add_system(assert_names);
|
||||
/// # assert_schedule.add_systems(assert_names);
|
||||
/// #
|
||||
/// # setup_schedule.run(&mut world);
|
||||
/// # assert_schedule.run(&mut world);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{
|
||||
archetype::ArchetypeComponentId,
|
||||
change_detection::MAX_CHANGE_AGE,
|
||||
component::ComponentId,
|
||||
component::{ComponentId, Tick},
|
||||
query::Access,
|
||||
system::{
|
||||
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, In, IntoSystem,
|
||||
|
@ -95,7 +94,7 @@ where
|
|||
|
||||
fn run(&mut self, input: Self::In, world: &mut World) -> Self::Out {
|
||||
let saved_last_tick = world.last_change_tick;
|
||||
world.last_change_tick = self.system_meta.last_change_tick;
|
||||
world.last_change_tick = self.system_meta.last_run;
|
||||
|
||||
let params = F::Param::get_param(
|
||||
self.param_state.as_mut().expect(PARAM_MESSAGE),
|
||||
|
@ -104,7 +103,7 @@ where
|
|||
let out = self.func.run(world, input, params);
|
||||
|
||||
let change_tick = world.change_tick.get_mut();
|
||||
self.system_meta.last_change_tick = *change_tick;
|
||||
self.system_meta.last_run.set(*change_tick);
|
||||
*change_tick = change_tick.wrapping_add(1);
|
||||
world.last_change_tick = saved_last_tick;
|
||||
|
||||
|
@ -116,12 +115,12 @@ where
|
|||
true
|
||||
}
|
||||
|
||||
fn get_last_change_tick(&self) -> u32 {
|
||||
self.system_meta.last_change_tick
|
||||
fn get_last_run(&self) -> Tick {
|
||||
self.system_meta.last_run
|
||||
}
|
||||
|
||||
fn set_last_change_tick(&mut self, last_change_tick: u32) {
|
||||
self.system_meta.last_change_tick = last_change_tick;
|
||||
fn set_last_run(&mut self, last_run: Tick) {
|
||||
self.system_meta.last_run = last_run;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -134,16 +133,16 @@ where
|
|||
#[inline]
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.world_id = Some(world.id());
|
||||
self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE);
|
||||
self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);
|
||||
self.param_state = Some(F::Param::init(world, &mut self.system_meta));
|
||||
}
|
||||
|
||||
fn update_archetype_component_access(&mut self, _world: &World) {}
|
||||
|
||||
#[inline]
|
||||
fn check_change_tick(&mut self, change_tick: u32) {
|
||||
fn check_change_tick(&mut self, change_tick: Tick) {
|
||||
check_system_change_tick(
|
||||
&mut self.system_meta.last_change_tick,
|
||||
&mut self.system_meta.last_run,
|
||||
change_tick,
|
||||
self.system_meta.name.as_ref(),
|
||||
);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::{
|
||||
archetype::{ArchetypeComponentId, ArchetypeGeneration, ArchetypeId},
|
||||
change_detection::MAX_CHANGE_AGE,
|
||||
component::ComponentId,
|
||||
component::{ComponentId, Tick},
|
||||
prelude::FromWorld,
|
||||
query::{Access, FilteredAccessSet},
|
||||
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
|
||||
|
@ -22,7 +21,7 @@ pub struct SystemMeta {
|
|||
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
|
||||
// SystemParams from overriding each other
|
||||
is_send: bool,
|
||||
pub(crate) last_change_tick: u32,
|
||||
pub(crate) last_run: Tick,
|
||||
}
|
||||
|
||||
impl SystemMeta {
|
||||
|
@ -32,7 +31,7 @@ impl SystemMeta {
|
|||
archetype_component_access: Access::default(),
|
||||
component_access_set: FilteredAccessSet::default(),
|
||||
is_send: true,
|
||||
last_change_tick: 0,
|
||||
last_run: Tick::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +150,7 @@ pub struct SystemState<Param: SystemParam + 'static> {
|
|||
impl<Param: SystemParam> SystemState<Param> {
|
||||
pub fn new(world: &mut World) -> Self {
|
||||
let mut meta = SystemMeta::new::<Param>();
|
||||
meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE);
|
||||
meta.last_run = world.change_tick().relative_to(Tick::MAX);
|
||||
let param_state = Param::init_state(world, &mut meta);
|
||||
Self {
|
||||
meta,
|
||||
|
@ -288,10 +287,10 @@ impl<Param: SystemParam> SystemState<Param> {
|
|||
unsafe fn fetch<'w, 's>(
|
||||
&'s mut self,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> SystemParamItem<'w, 's, Param> {
|
||||
let param = Param::get_param(&mut self.param_state, &self.meta, world, change_tick);
|
||||
self.meta.last_change_tick = change_tick;
|
||||
self.meta.last_run = change_tick;
|
||||
param
|
||||
}
|
||||
}
|
||||
|
@ -368,6 +367,9 @@ pub struct In<In>(pub In);
|
|||
/// becomes the functions [`In`] tagged parameter or `()` if no such parameter exists.
|
||||
///
|
||||
/// [`FunctionSystem`] must be `.initialized` before they can be run.
|
||||
///
|
||||
/// The [`Clone`] implementation for [`FunctionSystem`] returns a new instance which
|
||||
/// is NOT initialized. The cloned system must also be `.initialized` before it can be run.
|
||||
pub struct FunctionSystem<Marker, F>
|
||||
where
|
||||
F: SystemParamFunction<Marker>,
|
||||
|
@ -381,6 +383,23 @@ where
|
|||
marker: PhantomData<fn() -> Marker>,
|
||||
}
|
||||
|
||||
// De-initializes the cloned system.
|
||||
impl<Marker, F> Clone for FunctionSystem<Marker, F>
|
||||
where
|
||||
F: SystemParamFunction<Marker> + Clone,
|
||||
{
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
func: self.func.clone(),
|
||||
param_state: None,
|
||||
system_meta: SystemMeta::new::<F>(),
|
||||
world_id: None,
|
||||
archetype_generation: ArchetypeGeneration::initial(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IsFunctionSystem;
|
||||
|
||||
impl<Marker, F> IntoSystem<F::In, F::Out, (IsFunctionSystem, Marker)> for F
|
||||
|
@ -464,16 +483,16 @@ where
|
|||
change_tick,
|
||||
);
|
||||
let out = self.func.run(input, params);
|
||||
self.system_meta.last_change_tick = change_tick;
|
||||
self.system_meta.last_run = change_tick;
|
||||
out
|
||||
}
|
||||
|
||||
fn get_last_change_tick(&self) -> u32 {
|
||||
self.system_meta.last_change_tick
|
||||
fn get_last_run(&self) -> Tick {
|
||||
self.system_meta.last_run
|
||||
}
|
||||
|
||||
fn set_last_change_tick(&mut self, last_change_tick: u32) {
|
||||
self.system_meta.last_change_tick = last_change_tick;
|
||||
fn set_last_run(&mut self, last_run: Tick) {
|
||||
self.system_meta.last_run = last_run;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -485,7 +504,7 @@ where
|
|||
#[inline]
|
||||
fn initialize(&mut self, world: &mut World) {
|
||||
self.world_id = Some(world.id());
|
||||
self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE);
|
||||
self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);
|
||||
self.param_state = Some(F::Param::init_state(world, &mut self.system_meta));
|
||||
}
|
||||
|
||||
|
@ -507,9 +526,9 @@ where
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn check_change_tick(&mut self, change_tick: u32) {
|
||||
fn check_change_tick(&mut self, change_tick: Tick) {
|
||||
check_system_change_tick(
|
||||
&mut self.system_meta.last_change_tick,
|
||||
&mut self.system_meta.last_run,
|
||||
change_tick,
|
||||
self.system_meta.name.as_ref(),
|
||||
);
|
||||
|
|
|
@ -55,14 +55,18 @@
|
|||
//!
|
||||
//! ```
|
||||
//! # use bevy_ecs::prelude::*;
|
||||
//! # let mut app = Schedule::new();
|
||||
//! // Prints "Hello, World!" each frame.
|
||||
//! app
|
||||
//! .add_system(print_first.before(print_mid))
|
||||
//! .add_system(print_mid)
|
||||
//! .add_system(print_last.after(print_mid));
|
||||
//! # let mut schedule = Schedule::new();
|
||||
//! # let mut world = World::new();
|
||||
//! # app.run(&mut world);
|
||||
//! // Configure these systems to run in order using `chain()`.
|
||||
//! schedule.add_systems((print_first, print_last).chain());
|
||||
//! // Prints "HelloWorld!"
|
||||
//! schedule.run(&mut world);
|
||||
//!
|
||||
//! // Configure this system to run in between the other two systems
|
||||
//! // using explicit dependencies.
|
||||
//! schedule.add_systems(print_mid.after(print_first).before(print_last));
|
||||
//! // Prints "Hello, World!"
|
||||
//! schedule.run(&mut world);
|
||||
//!
|
||||
//! fn print_first() {
|
||||
//! print!("Hello");
|
||||
|
@ -119,17 +123,40 @@ pub use system::*;
|
|||
pub use system_param::*;
|
||||
pub use system_piping::*;
|
||||
|
||||
use crate::world::World;
|
||||
|
||||
/// Ensure that a given function is a [system](System).
|
||||
///
|
||||
/// This should be used when writing doc examples,
|
||||
/// to confirm that systems used in an example are
|
||||
/// valid systems.
|
||||
pub fn assert_is_system<In, Out, Marker, S: IntoSystem<In, Out, Marker>>(sys: S) {
|
||||
if false {
|
||||
// Check it can be converted into a system
|
||||
// TODO: This should ensure that the system has no conflicting system params
|
||||
IntoSystem::into_system(sys);
|
||||
}
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following example will panic when run since the
|
||||
/// system's parameters mutably access the same component
|
||||
/// multiple times.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// # use bevy_ecs::{prelude::*, system::assert_is_system};
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Transform;
|
||||
/// #
|
||||
/// fn my_system(query1: Query<&mut Transform>, query2: Query<&mut Transform>) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// assert_is_system(my_system);
|
||||
/// ```
|
||||
pub fn assert_is_system<In: 'static, Out: 'static, Marker>(
|
||||
system: impl IntoSystem<In, Out, Marker>,
|
||||
) {
|
||||
let mut system = IntoSystem::into_system(system);
|
||||
|
||||
// Initialize the system, which will panic if the system has access conflicts.
|
||||
let mut world = World::new();
|
||||
system.initialize(&mut world);
|
||||
}
|
||||
|
||||
/// Ensure that a given function is a [read-only system](ReadOnlySystem).
|
||||
|
@ -137,15 +164,30 @@ pub fn assert_is_system<In, Out, Marker, S: IntoSystem<In, Out, Marker>>(sys: S)
|
|||
/// This should be used when writing doc examples,
|
||||
/// to confirm that systems used in an example are
|
||||
/// valid systems.
|
||||
pub fn assert_is_read_only_system<In, Out, Marker, S: IntoSystem<In, Out, Marker>>(sys: S)
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// The following example will fail to compile
|
||||
/// since the system accesses a component mutably.
|
||||
///
|
||||
/// ```compile_fail
|
||||
/// # use bevy_ecs::{prelude::*, system::assert_is_read_only_system};
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Transform;
|
||||
/// #
|
||||
/// fn my_system(query: Query<&mut Transform>) {
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// assert_is_read_only_system(my_system);
|
||||
/// ```
|
||||
pub fn assert_is_read_only_system<In: 'static, Out: 'static, Marker, S>(system: S)
|
||||
where
|
||||
S: IntoSystem<In, Out, Marker>,
|
||||
S::System: ReadOnlySystem,
|
||||
{
|
||||
if false {
|
||||
// Check it can be converted into a system
|
||||
// TODO: This should ensure that the system has no conflicting system params
|
||||
IntoSystem::into_system(sys);
|
||||
}
|
||||
assert_is_system(system);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -157,12 +199,12 @@ mod tests {
|
|||
archetype::{ArchetypeComponentId, Archetypes},
|
||||
bundle::Bundles,
|
||||
change_detection::DetectChanges,
|
||||
component::{Component, Components},
|
||||
component::{Component, Components, Tick},
|
||||
entity::{Entities, Entity},
|
||||
prelude::AnyOf,
|
||||
query::{Added, Changed, Or, With, Without},
|
||||
removal_detection::RemovedComponents,
|
||||
schedule::{apply_system_buffers, IntoSystemConfig, Schedule},
|
||||
schedule::{apply_system_buffers, IntoSystemConfigs, Schedule},
|
||||
system::{
|
||||
Commands, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, QueryComponentError,
|
||||
Res, ResMut, Resource, System, SystemState,
|
||||
|
@ -210,7 +252,7 @@ mod tests {
|
|||
|
||||
fn run_system<Marker, S: IntoSystem<(), (), Marker>>(world: &mut World, system: S) {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_system(system);
|
||||
schedule.add_systems(system);
|
||||
schedule.run(world);
|
||||
}
|
||||
|
||||
|
@ -272,6 +314,60 @@ mod tests {
|
|||
assert_eq!(*world.resource::<SystemRan>(), SystemRan::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_many_is_ordered() {
|
||||
use crate::system::Resource;
|
||||
const ENTITIES_COUNT: usize = 1000;
|
||||
|
||||
#[derive(Resource)]
|
||||
struct EntitiesArray(Vec<Entity>);
|
||||
|
||||
fn query_system(
|
||||
mut ran: ResMut<SystemRan>,
|
||||
entities_array: Res<EntitiesArray>,
|
||||
q: Query<&W<usize>>,
|
||||
) {
|
||||
let entities_array: [Entity; ENTITIES_COUNT] =
|
||||
entities_array.0.clone().try_into().unwrap();
|
||||
|
||||
for (i, w) in (0..ENTITIES_COUNT).zip(q.get_many(entities_array).unwrap()) {
|
||||
assert_eq!(i, w.0);
|
||||
}
|
||||
|
||||
*ran = SystemRan::Yes;
|
||||
}
|
||||
|
||||
fn query_system_mut(
|
||||
mut ran: ResMut<SystemRan>,
|
||||
entities_array: Res<EntitiesArray>,
|
||||
mut q: Query<&mut W<usize>>,
|
||||
) {
|
||||
let entities_array: [Entity; ENTITIES_COUNT] =
|
||||
entities_array.0.clone().try_into().unwrap();
|
||||
|
||||
#[allow(unused_mut)]
|
||||
for (i, mut w) in (0..ENTITIES_COUNT).zip(q.get_many_mut(entities_array).unwrap()) {
|
||||
assert_eq!(i, w.0);
|
||||
}
|
||||
|
||||
*ran = SystemRan::Yes;
|
||||
}
|
||||
|
||||
let mut world = World::default();
|
||||
world.insert_resource(SystemRan::No);
|
||||
let entity_ids = (0..ENTITIES_COUNT)
|
||||
.map(|i| world.spawn(W(i)).id())
|
||||
.collect();
|
||||
world.insert_resource(EntitiesArray(entity_ids));
|
||||
|
||||
run_system(&mut world, query_system);
|
||||
assert_eq!(*world.resource::<SystemRan>(), SystemRan::Yes);
|
||||
|
||||
world.insert_resource(SystemRan::No);
|
||||
run_system(&mut world, query_system_mut);
|
||||
assert_eq!(*world.resource::<SystemRan>(), SystemRan::Yes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn or_param_set_system() {
|
||||
// Regression test for issue #762
|
||||
|
@ -334,9 +430,7 @@ mod tests {
|
|||
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
schedule.add_system(incr_e_on_flip);
|
||||
schedule.add_system(apply_system_buffers.after(incr_e_on_flip));
|
||||
schedule.add_system(World::clear_trackers.after(apply_system_buffers));
|
||||
schedule.add_systems((incr_e_on_flip, apply_system_buffers, World::clear_trackers).chain());
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<Added>().0, 1);
|
||||
|
@ -1227,7 +1321,7 @@ mod tests {
|
|||
let world2 = World::new();
|
||||
let qstate = world1.query::<()>();
|
||||
// SAFETY: doesnt access anything
|
||||
let query = unsafe { Query::new(&world2, &qstate, 0, 0, false) };
|
||||
let query = unsafe { Query::new(&world2, &qstate, Tick::new(0), Tick::new(0), false) };
|
||||
query.iter();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
component::Component,
|
||||
component::{Component, Tick},
|
||||
entity::Entity,
|
||||
query::{
|
||||
BatchingStrategy, QueryCombinationIter, QueryEntityError, QueryIter, QueryManyIter,
|
||||
|
@ -276,8 +276,8 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug};
|
|||
pub struct Query<'world, 'state, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> {
|
||||
world: &'world World,
|
||||
state: &'state QueryState<Q, F>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
// SAFETY: This is used to ensure that `get_component_mut::<C>` properly fails when a Query writes C
|
||||
// and gets converted to a read-only query using `to_readonly`. Without checking this, `get_component_mut` relies on
|
||||
// QueryState's archetype_component_access, which will continue allowing write access to C after being cast to
|
||||
|
@ -288,7 +288,7 @@ pub struct Query<'world, 'state, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> {
|
|||
|
||||
impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> std::fmt::Debug for Query<'w, 's, Q, F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Query {{ matched entities: {}, world: {:?}, state: {:?}, last_change_tick: {}, change_tick: {} }}", self.iter().count(), self.world, self.state, self.last_change_tick, self.change_tick)
|
||||
write!(f, "Query {{ matched entities: {}, world: {:?}, state: {:?}, last_run: {:?}, this_run: {:?} }}", self.iter().count(), self.world, self.state, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,8 +307,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
pub(crate) unsafe fn new(
|
||||
world: &'w World,
|
||||
state: &'s QueryState<Q, F>,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
force_read_only_component_access: bool,
|
||||
) -> Self {
|
||||
state.validate_world(world);
|
||||
|
@ -317,8 +317,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
force_read_only_component_access,
|
||||
world,
|
||||
state,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run,
|
||||
this_run,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,8 +334,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
Query::new(
|
||||
self.world,
|
||||
new_state,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
// SAFETY: this must be set to true or `get_component_mut` will be unsound. See the comments
|
||||
// on this field for more details
|
||||
true,
|
||||
|
@ -372,11 +372,9 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state.as_readonly().iter_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.as_readonly()
|
||||
.iter_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,7 +408,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state
|
||||
.iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick)
|
||||
.iter_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -442,8 +440,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
unsafe {
|
||||
self.state.as_readonly().iter_combinations_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -474,11 +472,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state.iter_combinations_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.iter_combinations_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -532,8 +527,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
self.state.as_readonly().iter_many_unchecked_manual(
|
||||
entities,
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -585,8 +580,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
self.state.iter_many_unchecked_manual(
|
||||
entities,
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -606,7 +601,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SEMI-SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
self.state
|
||||
.iter_unchecked_manual(self.world, self.last_change_tick, self.change_tick)
|
||||
.iter_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
|
||||
/// Iterates over all possible combinations of `K` query items without repetition.
|
||||
|
@ -625,11 +620,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
) -> QueryCombinationIter<'_, 's, Q, F, K> {
|
||||
// SEMI-SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
self.state.iter_combinations_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.iter_combinations_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
|
||||
/// Returns an [`Iterator`] over the query items generated from an [`Entity`] list.
|
||||
|
@ -650,12 +642,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
where
|
||||
EntityList::Item: Borrow<Entity>,
|
||||
{
|
||||
self.state.iter_many_unchecked_manual(
|
||||
entities,
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.iter_many_unchecked_manual(entities, self.world, self.last_run, self.this_run)
|
||||
}
|
||||
|
||||
/// Runs `f` on each read-only query item.
|
||||
|
@ -690,8 +678,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
self.state.as_readonly().for_each_unchecked_manual(
|
||||
self.world,
|
||||
f,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
@ -725,12 +713,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SAFETY: system runs without conflicts with other systems. same-system queries have runtime
|
||||
// borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state.for_each_unchecked_manual(
|
||||
self.world,
|
||||
f,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
);
|
||||
self.state
|
||||
.for_each_unchecked_manual(self.world, f, self.last_run, self.this_run);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -744,6 +728,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
QueryParIter {
|
||||
world: self.world,
|
||||
state: self.state.as_readonly(),
|
||||
last_run: self.last_run,
|
||||
this_run: self.this_run,
|
||||
batching_strategy: BatchingStrategy::new(),
|
||||
}
|
||||
}
|
||||
|
@ -758,6 +744,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
QueryParIter {
|
||||
world: self.world,
|
||||
state: self.state,
|
||||
last_run: self.last_run,
|
||||
this_run: self.this_run,
|
||||
batching_strategy: BatchingStrategy::new(),
|
||||
}
|
||||
}
|
||||
|
@ -801,14 +789,15 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
self.state.as_readonly().get_unchecked_manual(
|
||||
self.world,
|
||||
entity,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the read-only query items for the given array of [`Entity`].
|
||||
///
|
||||
/// The returned query items are in the same order as the input.
|
||||
/// In case of a nonexisting entity or mismatched component, a [`QueryEntityError`] is returned instead.
|
||||
/// The elements of the array do not need to be unique, unlike `get_many_mut`.
|
||||
///
|
||||
|
@ -823,12 +812,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
) -> Result<[ROQueryItem<'_, Q>; N], QueryEntityError> {
|
||||
// SAFETY: it is the scheduler's responsibility to ensure that `Query` is never handed out on the wrong `World`.
|
||||
unsafe {
|
||||
self.state.get_many_read_only_manual(
|
||||
self.world,
|
||||
entities,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.get_many_read_only_manual(self.world, entities, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -910,17 +895,14 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state.get_unchecked_manual(
|
||||
self.world,
|
||||
entity,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.get_unchecked_manual(self.world, entity, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the query items for the given array of [`Entity`].
|
||||
///
|
||||
/// The returned query items are in the same order as the input.
|
||||
/// In case of a nonexisting entity, duplicate entities or mismatched component, a [`QueryEntityError`] is returned instead.
|
||||
///
|
||||
/// # See also
|
||||
|
@ -934,12 +916,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
) -> Result<[Q::Item<'_>; N], QueryEntityError> {
|
||||
// SAFETY: scheduler ensures safe Query world access
|
||||
unsafe {
|
||||
self.state.get_many_unchecked_manual(
|
||||
self.world,
|
||||
entities,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.get_many_unchecked_manual(self.world, entities, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1013,7 +991,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SEMI-SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
self.state
|
||||
.get_unchecked_manual(self.world, entity, self.last_change_tick, self.change_tick)
|
||||
.get_unchecked_manual(self.world, entity, self.last_run, self.this_run)
|
||||
}
|
||||
|
||||
/// Returns a shared reference to the component `T` of the given [`Entity`].
|
||||
|
@ -1151,7 +1129,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
.has_write(archetype_component)
|
||||
{
|
||||
entity_ref
|
||||
.get_mut_using_ticks::<T>(self.last_change_tick, self.change_tick)
|
||||
.get_mut_using_ticks::<T>(self.last_run, self.this_run)
|
||||
.ok_or(QueryComponentError::MissingComponent)
|
||||
} else {
|
||||
Err(QueryComponentError::MissingWriteAccess)
|
||||
|
@ -1227,8 +1205,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
unsafe {
|
||||
self.state.as_readonly().get_single_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1296,11 +1274,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// the query ensures mutable access to the components it accesses, and the query
|
||||
// is uniquely borrowed
|
||||
unsafe {
|
||||
self.state.get_single_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.get_single_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1327,7 +1302,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.state
|
||||
.is_empty(self.world, self.last_change_tick, self.change_tick)
|
||||
.is_empty(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
|
||||
/// Returns `true` if the given [`Entity`] matches the query.
|
||||
|
@ -1358,7 +1333,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
unsafe {
|
||||
self.state
|
||||
.as_nop()
|
||||
.get_unchecked_manual(self.world, entity, self.last_change_tick, self.change_tick)
|
||||
.get_unchecked_manual(self.world, entity, self.last_run, self.this_run)
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
@ -1459,8 +1434,8 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
self.state.as_readonly().get_unchecked_manual(
|
||||
self.world,
|
||||
entity,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
self.last_run,
|
||||
self.this_run,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1493,11 +1468,9 @@ impl<'w, 's, Q: ReadOnlyWorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> {
|
|||
// SAFETY: system runs without conflicts with other systems.
|
||||
// same-system queries have runtime borrow checks when they conflict
|
||||
unsafe {
|
||||
self.state.as_readonly().iter_unchecked_manual(
|
||||
self.world,
|
||||
self.last_change_tick,
|
||||
self.change_tick,
|
||||
)
|
||||
self.state
|
||||
.as_readonly()
|
||||
.iter_unchecked_manual(self.world, self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use bevy_utils::tracing::warn;
|
||||
use core::fmt::Debug;
|
||||
|
||||
use crate::{
|
||||
archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId,
|
||||
query::Access, world::World,
|
||||
};
|
||||
use crate::component::Tick;
|
||||
use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World};
|
||||
|
||||
use std::any::TypeId;
|
||||
use std::borrow::Cow;
|
||||
|
@ -19,7 +17,7 @@ use std::borrow::Cow;
|
|||
///
|
||||
/// Systems are executed in parallel, in opportunistic order; data access is managed automatically.
|
||||
/// It's possible to specify explicit execution order between specific systems,
|
||||
/// see [`IntoSystemConfig`](crate::schedule::IntoSystemConfig).
|
||||
/// see [`IntoSystemConfigs`](crate::schedule::IntoSystemConfigs).
|
||||
pub trait System: Send + Sync + 'static {
|
||||
/// The system's input. See [`In`](crate::system::In) for
|
||||
/// [`FunctionSystem`](crate::system::FunctionSystem)s.
|
||||
|
@ -63,19 +61,20 @@ pub trait System: Send + Sync + 'static {
|
|||
fn initialize(&mut self, _world: &mut World);
|
||||
/// Update the system's archetype component [`Access`].
|
||||
fn update_archetype_component_access(&mut self, world: &World);
|
||||
fn check_change_tick(&mut self, change_tick: u32);
|
||||
fn check_change_tick(&mut self, change_tick: Tick);
|
||||
/// Returns the system's default [system sets](crate::schedule::SystemSet).
|
||||
fn default_system_sets(&self) -> Vec<Box<dyn crate::schedule::SystemSet>> {
|
||||
Vec::new()
|
||||
}
|
||||
/// Gets the system's last change tick
|
||||
fn get_last_change_tick(&self) -> u32;
|
||||
/// Sets the system's last change tick
|
||||
/// Gets the tick indicating the last time this system ran.
|
||||
fn get_last_run(&self) -> Tick;
|
||||
/// Overwrites the tick indicating the last time this system ran.
|
||||
///
|
||||
/// # Warning
|
||||
/// This is a complex and error-prone operation, that can have unexpected consequences on any system relying on this code.
|
||||
/// However, it can be an essential escape hatch when, for example,
|
||||
/// you are trying to synchronize representations using change detection and need to avoid infinite recursion.
|
||||
fn set_last_change_tick(&mut self, last_change_tick: u32);
|
||||
fn set_last_run(&mut self, last_run: Tick);
|
||||
}
|
||||
|
||||
/// [`System`] types that do not modify the [`World`] when run.
|
||||
|
@ -91,23 +90,14 @@ pub unsafe trait ReadOnlySystem: System {}
|
|||
/// A convenience type alias for a boxed [`System`] trait object.
|
||||
pub type BoxedSystem<In = (), Out = ()> = Box<dyn System<In = In, Out = Out>>;
|
||||
|
||||
pub(crate) fn check_system_change_tick(
|
||||
last_change_tick: &mut u32,
|
||||
change_tick: u32,
|
||||
system_name: &str,
|
||||
) {
|
||||
let age = change_tick.wrapping_sub(*last_change_tick);
|
||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
||||
// so long as this check always runs before that can happen.
|
||||
if age > MAX_CHANGE_AGE {
|
||||
pub(crate) fn check_system_change_tick(last_run: &mut Tick, this_run: Tick, system_name: &str) {
|
||||
if last_run.check_tick(this_run) {
|
||||
let age = this_run.relative_to(*last_run).get();
|
||||
warn!(
|
||||
"System '{}' has not run for {} ticks. \
|
||||
"System '{system_name}' has not run for {age} ticks. \
|
||||
Changes older than {} ticks will not be detected.",
|
||||
system_name,
|
||||
age,
|
||||
MAX_CHANGE_AGE - 1,
|
||||
Tick::MAX.get() - 1,
|
||||
);
|
||||
*last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ use crate::{
|
|||
archetype::{Archetype, Archetypes},
|
||||
bundle::Bundles,
|
||||
change_detection::{Ticks, TicksMut},
|
||||
component::{ComponentId, ComponentTicks, Components},
|
||||
component::{ComponentId, ComponentTicks, Components, Tick},
|
||||
entity::Entities,
|
||||
query::{
|
||||
Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery,
|
||||
|
@ -89,38 +89,6 @@ use std::{
|
|||
/// This will most commonly occur when working with `SystemParam`s generically, as the requirement
|
||||
/// has not been proven to the compiler.
|
||||
///
|
||||
/// # `!Sync` Resources
|
||||
/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync`
|
||||
/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only
|
||||
/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple
|
||||
/// threads.
|
||||
///
|
||||
/// This will fail to compile since `RefCell` is `!Sync`.
|
||||
/// ```compile_fail
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use bevy_ecs::system::Resource;
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct NotSync {
|
||||
/// counter: RefCell<usize>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This will compile since the `RefCell` is wrapped with `SyncCell`.
|
||||
/// ```
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use bevy_ecs::system::Resource;
|
||||
/// use bevy_utils::synccell::SyncCell;
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct ActuallySync {
|
||||
/// counter: SyncCell<RefCell<usize>>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`SyncCell`]: bevy_utils::synccell::SyncCell
|
||||
/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The implementor must ensure the following is true.
|
||||
|
@ -169,7 +137,7 @@ pub unsafe trait SystemParam: Sized {
|
|||
state: &'state mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'world World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'world, 'state>;
|
||||
}
|
||||
|
||||
|
@ -227,15 +195,9 @@ unsafe impl<Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> SystemPara
|
|||
state: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
Query::new(
|
||||
world,
|
||||
state,
|
||||
system_meta.last_change_tick,
|
||||
change_tick,
|
||||
false,
|
||||
)
|
||||
Query::new(world, state, system_meta.last_run, change_tick, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -371,7 +333,7 @@ pub struct ParamSet<'w, 's, T: SystemParam> {
|
|||
param_states: &'s mut T::State,
|
||||
world: &'w World,
|
||||
system_meta: SystemMeta,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
}
|
||||
|
||||
impl_param_set!();
|
||||
|
@ -402,10 +364,41 @@ impl_param_set!();
|
|||
/// resource.value = 0;
|
||||
/// assert_eq!(resource.value, 0);
|
||||
/// }
|
||||
/// # schedule.add_system(read_resource_system);
|
||||
/// # schedule.add_system(write_resource_system.after(read_resource_system));
|
||||
/// # schedule.add_systems((read_resource_system, write_resource_system).chain());
|
||||
/// # schedule.run(&mut world);
|
||||
/// ```
|
||||
///
|
||||
/// # `!Sync` Resources
|
||||
/// A `!Sync` type cannot implement `Resource`. However, it is possible to wrap a `Send` but not `Sync`
|
||||
/// type in [`SyncCell`] or the currently unstable [`Exclusive`] to make it `Sync`. This forces only
|
||||
/// having mutable access (`&mut T` only, never `&T`), but makes it safe to reference across multiple
|
||||
/// threads.
|
||||
///
|
||||
/// This will fail to compile since `RefCell` is `!Sync`.
|
||||
/// ```compile_fail
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use bevy_ecs::system::Resource;
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct NotSync {
|
||||
/// counter: RefCell<usize>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This will compile since the `RefCell` is wrapped with `SyncCell`.
|
||||
/// ```
|
||||
/// # use std::cell::RefCell;
|
||||
/// # use bevy_ecs::system::Resource;
|
||||
/// use bevy_utils::synccell::SyncCell;
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct ActuallySync {
|
||||
/// counter: SyncCell<RefCell<usize>>,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`SyncCell`]: bevy_utils::synccell::SyncCell
|
||||
/// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html
|
||||
pub trait Resource: Send + Sync + 'static {}
|
||||
|
||||
// SAFETY: Res only reads a single World resource
|
||||
|
@ -445,7 +438,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
let (ptr, ticks) = world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -462,8 +455,8 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
|
|||
ticks: Ticks {
|
||||
added: ticks.added.deref(),
|
||||
changed: ticks.changed.deref(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -486,7 +479,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -496,8 +489,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option<Res<'a, T>> {
|
|||
ticks: Ticks {
|
||||
added: ticks.added.deref(),
|
||||
changed: ticks.changed.deref(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -540,7 +533,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
let value = world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -557,8 +550,8 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
|
|||
ticks: TicksMut {
|
||||
added: value.ticks.added,
|
||||
changed: value.ticks.changed,
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -578,7 +571,7 @@ unsafe impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -588,8 +581,8 @@ unsafe impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> {
|
|||
ticks: TicksMut {
|
||||
added: value.ticks.added,
|
||||
changed: value.ticks.changed,
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -631,7 +624,7 @@ unsafe impl SystemParam for &'_ World {
|
|||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
}
|
||||
|
@ -681,7 +674,7 @@ unsafe impl SystemParam for &'_ World {
|
|||
/// move |mut val| val.0 = value.0
|
||||
/// }
|
||||
///
|
||||
/// // .add_system(reset_to_system(my_config))
|
||||
/// // .add_systems(reset_to_system(my_config))
|
||||
/// # assert_is_system(reset_to_system(Config(10)));
|
||||
/// ```
|
||||
pub struct Local<'s, T: FromWorld + Send + 'static>(pub(crate) &'s mut T);
|
||||
|
@ -752,7 +745,7 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> {
|
|||
state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
_world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
Local(state.get())
|
||||
}
|
||||
|
@ -867,10 +860,8 @@ pub trait SystemBuffer: FromWorld + Send + 'static {
|
|||
/// });
|
||||
///
|
||||
/// let mut schedule = Schedule::new();
|
||||
/// schedule
|
||||
/// // These two systems have no conflicts and will run in parallel.
|
||||
/// .add_system(alert_criminal)
|
||||
/// .add_system(alert_monster);
|
||||
/// // These two systems have no conflicts and will run in parallel.
|
||||
/// schedule.add_systems((alert_criminal, alert_monster));
|
||||
///
|
||||
/// // There are no criminals or monsters, so the alarm is not sounded.
|
||||
/// schedule.run(&mut world);
|
||||
|
@ -927,7 +918,7 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
|
|||
state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
_world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
Deferred(state.get())
|
||||
}
|
||||
|
@ -948,8 +939,8 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
|
|||
pub struct NonSend<'w, T: 'static> {
|
||||
pub(crate) value: &'w T,
|
||||
ticks: ComponentTicks,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
}
|
||||
|
||||
// SAFETY: Only reads a single World non-send resource
|
||||
|
@ -967,13 +958,12 @@ where
|
|||
impl<'w, T: 'static> NonSend<'w, T> {
|
||||
/// Returns `true` if the resource was added after the system last ran.
|
||||
pub fn is_added(&self) -> bool {
|
||||
self.ticks.is_added(self.last_change_tick, self.change_tick)
|
||||
self.ticks.is_added(self.last_run, self.this_run)
|
||||
}
|
||||
|
||||
/// Returns `true` if the resource was added or mutably dereferenced after the system last ran.
|
||||
pub fn is_changed(&self) -> bool {
|
||||
self.ticks
|
||||
.is_changed(self.last_change_tick, self.change_tick)
|
||||
self.ticks.is_changed(self.last_run, self.this_run)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -992,8 +982,8 @@ impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
|
|||
added: nsm.ticks.added.to_owned(),
|
||||
changed: nsm.ticks.changed.to_owned(),
|
||||
},
|
||||
change_tick: nsm.ticks.change_tick,
|
||||
last_change_tick: nsm.ticks.last_change_tick,
|
||||
this_run: nsm.ticks.this_run,
|
||||
last_run: nsm.ticks.last_run,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1034,7 +1024,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
let (ptr, ticks) = world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -1050,8 +1040,8 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
|
|||
NonSend {
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.read(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1073,7 +1063,7 @@ unsafe impl<T: 'static> SystemParam for Option<NonSend<'_, T>> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -1081,8 +1071,8 @@ unsafe impl<T: 'static> SystemParam for Option<NonSend<'_, T>> {
|
|||
.map(|(ptr, ticks)| NonSend {
|
||||
value: ptr.deref(),
|
||||
ticks: ticks.read(),
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1126,7 +1116,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
let (ptr, ticks) = world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
|
@ -1140,7 +1130,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
|
|||
});
|
||||
NonSendMut {
|
||||
value: ptr.assert_unique().deref_mut(),
|
||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_change_tick, change_tick),
|
||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1159,14 +1149,14 @@ unsafe impl<'a, T: 'static> SystemParam for Option<NonSendMut<'a, T>> {
|
|||
&mut component_id: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world
|
||||
.as_unsafe_world_cell_migration_internal()
|
||||
.get_non_send_with_ticks(component_id)
|
||||
.map(|(ptr, ticks)| NonSendMut {
|
||||
value: ptr.assert_unique().deref_mut(),
|
||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_change_tick, change_tick),
|
||||
ticks: TicksMut::from_tick_cells(ticks, system_meta.last_run, change_tick),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1186,7 +1176,7 @@ unsafe impl<'a> SystemParam for &'a Archetypes {
|
|||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.archetypes()
|
||||
}
|
||||
|
@ -1207,7 +1197,7 @@ unsafe impl<'a> SystemParam for &'a Components {
|
|||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.components()
|
||||
}
|
||||
|
@ -1228,7 +1218,7 @@ unsafe impl<'a> SystemParam for &'a Entities {
|
|||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.entities()
|
||||
}
|
||||
|
@ -1249,7 +1239,7 @@ unsafe impl<'a> SystemParam for &'a Bundles {
|
|||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.bundles()
|
||||
}
|
||||
|
@ -1258,29 +1248,29 @@ unsafe impl<'a> SystemParam for &'a Bundles {
|
|||
/// A [`SystemParam`] that reads the previous and current change ticks of the system.
|
||||
///
|
||||
/// A system's change ticks are updated each time it runs:
|
||||
/// - `last_change_tick` copies the previous value of `change_tick`
|
||||
/// - `change_tick` copies the current value of [`World::read_change_tick`]
|
||||
/// - `last_run` copies the previous value of `change_tick`
|
||||
/// - `this_run` copies the current value of [`World::read_change_tick`]
|
||||
///
|
||||
/// Component change ticks that are more recent than `last_change_tick` will be detected by the system.
|
||||
/// Component change ticks that are more recent than `last_run` will be detected by the system.
|
||||
/// Those can be read by calling [`last_changed`](crate::change_detection::DetectChanges::last_changed)
|
||||
/// on a [`Mut<T>`](crate::change_detection::Mut) or [`ResMut<T>`](crate::change_detection::ResMut).
|
||||
#[derive(Debug)]
|
||||
pub struct SystemChangeTick {
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
}
|
||||
|
||||
impl SystemChangeTick {
|
||||
/// Returns the current [`World`] change tick seen by the system.
|
||||
#[inline]
|
||||
pub fn change_tick(&self) -> u32 {
|
||||
self.change_tick
|
||||
pub fn this_run(&self) -> Tick {
|
||||
self.this_run
|
||||
}
|
||||
|
||||
/// Returns the [`World`] change tick seen by the system the previous time it ran.
|
||||
#[inline]
|
||||
pub fn last_change_tick(&self) -> u32 {
|
||||
self.last_change_tick
|
||||
pub fn last_run(&self) -> Tick {
|
||||
self.last_run
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1298,11 +1288,11 @@ unsafe impl SystemParam for SystemChangeTick {
|
|||
_state: &'s mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
_world: &'w World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
SystemChangeTick {
|
||||
last_change_tick: system_meta.last_change_tick,
|
||||
change_tick,
|
||||
last_run: system_meta.last_run,
|
||||
this_run: change_tick,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1368,7 +1358,7 @@ unsafe impl SystemParam for SystemName<'_> {
|
|||
name: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
_world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
SystemName { name }
|
||||
}
|
||||
|
@ -1410,7 +1400,7 @@ macro_rules! impl_system_param_tuple {
|
|||
state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
_world: &'w World,
|
||||
_change_tick: u32,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
|
||||
let ($($param,)*) = state;
|
||||
|
@ -1534,7 +1524,7 @@ unsafe impl<P: SystemParam + 'static> SystemParam for StaticSystemParam<'_, '_,
|
|||
state: &'state mut Self::State,
|
||||
system_meta: &SystemMeta,
|
||||
world: &'world World,
|
||||
change_tick: u32,
|
||||
change_tick: Tick,
|
||||
) -> Self::Item<'world, 'state> {
|
||||
// SAFETY: Defer to the safety of P::SystemParam
|
||||
StaticSystemParam(P::get_param(state, system_meta, world, change_tick))
|
||||
|
@ -1635,7 +1625,7 @@ mod tests {
|
|||
#[derive(SystemParam)]
|
||||
pub struct EncapsulatedParam<'w>(Res<'w, PrivateResource>);
|
||||
|
||||
// regression test for https://github.com/bevyengine/bevy/issues/7103.
|
||||
// Regression test for https://github.com/bevyengine/bevy/issues/7103.
|
||||
#[derive(SystemParam)]
|
||||
pub struct WhereParam<'w, 's, Q>
|
||||
where
|
||||
|
@ -1643,4 +1633,13 @@ mod tests {
|
|||
{
|
||||
_q: Query<'w, 's, Q, ()>,
|
||||
}
|
||||
|
||||
// Regression test for https://github.com/bevyengine/bevy/issues/1727.
|
||||
#[derive(SystemParam)]
|
||||
pub struct Collide<'w> {
|
||||
_x: Res<'w, FetchState>,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct FetchState;
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ pub mod adapter {
|
|||
///
|
||||
/// // Building a new schedule/app...
|
||||
/// let mut sched = Schedule::default();
|
||||
/// sched.add_system(
|
||||
/// sched.add_systems(
|
||||
/// // Panic if the load system returns an error.
|
||||
/// load_save_system.pipe(system_adapter::unwrap)
|
||||
/// )
|
||||
|
@ -169,7 +169,7 @@ pub mod adapter {
|
|||
///
|
||||
/// // Building a new schedule/app...
|
||||
/// let mut sched = Schedule::default();
|
||||
/// sched.add_system(
|
||||
/// sched.add_systems(
|
||||
/// // Prints system information.
|
||||
/// data_pipe_system.pipe(system_adapter::info)
|
||||
/// )
|
||||
|
@ -196,7 +196,7 @@ pub mod adapter {
|
|||
///
|
||||
/// // Building a new schedule/app...
|
||||
/// let mut sched = Schedule::default();
|
||||
/// sched.add_system(
|
||||
/// sched.add_systems(
|
||||
/// // Prints debug data from system.
|
||||
/// parse_message_system.pipe(system_adapter::dbg)
|
||||
/// )
|
||||
|
@ -223,7 +223,7 @@ pub mod adapter {
|
|||
///
|
||||
/// // Building a new schedule/app...
|
||||
/// # let mut sched = Schedule::default();
|
||||
/// sched.add_system(
|
||||
/// sched.add_systems(
|
||||
/// // Prints system warning if system returns an error.
|
||||
/// warning_pipe_system.pipe(system_adapter::warn)
|
||||
/// )
|
||||
|
@ -251,7 +251,7 @@ pub mod adapter {
|
|||
/// use bevy_ecs::prelude::*;
|
||||
/// // Building a new schedule/app...
|
||||
/// let mut sched = Schedule::default();
|
||||
/// sched.add_system(
|
||||
/// sched.add_systems(
|
||||
/// // Prints system error if system fails.
|
||||
/// parse_error_message_system.pipe(system_adapter::error)
|
||||
/// )
|
||||
|
@ -287,7 +287,7 @@ pub mod adapter {
|
|||
///
|
||||
/// // Building a new schedule/app...
|
||||
/// # let mut sched = Schedule::default(); sched
|
||||
/// .add_system(
|
||||
/// .add_systems(
|
||||
/// // If the system fails, just move on and try again next frame.
|
||||
/// fallible_system.pipe(system_adapter::ignore)
|
||||
/// )
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{
|
||||
archetype::{Archetype, ArchetypeId, Archetypes},
|
||||
bundle::{Bundle, BundleInfo},
|
||||
bundle::{Bundle, BundleInfo, BundleInserter, DynamicBundle},
|
||||
change_detection::MutUntyped,
|
||||
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
|
@ -279,6 +279,90 @@ impl<'w> EntityMut<'w> {
|
|||
self
|
||||
}
|
||||
|
||||
/// Inserts a dynamic [`Component`] into the entity.
|
||||
///
|
||||
/// This will overwrite any previous value(s) of the same component type.
|
||||
///
|
||||
/// You should prefer to use the typed API [`EntityMut::insert`] where possible.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - [`ComponentId`] must be from the same world as [`EntityMut`]
|
||||
/// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
|
||||
pub unsafe fn insert_by_id(
|
||||
&mut self,
|
||||
component_id: ComponentId,
|
||||
component: OwningPtr<'_>,
|
||||
) -> &mut Self {
|
||||
let change_tick = self.world.change_tick();
|
||||
|
||||
let bundles = &mut self.world.bundles;
|
||||
let components = &mut self.world.components;
|
||||
|
||||
let (bundle_info, storage_type) = bundles.init_component_info(components, component_id);
|
||||
let bundle_inserter = bundle_info.get_bundle_inserter(
|
||||
&mut self.world.entities,
|
||||
&mut self.world.archetypes,
|
||||
&mut self.world.components,
|
||||
&mut self.world.storages,
|
||||
self.location.archetype_id,
|
||||
change_tick,
|
||||
);
|
||||
|
||||
self.location = insert_dynamic_bundle(
|
||||
bundle_inserter,
|
||||
self.entity,
|
||||
self.location,
|
||||
Some(component).into_iter(),
|
||||
Some(storage_type).into_iter(),
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserts a dynamic [`Bundle`] into the entity.
|
||||
///
|
||||
/// This will overwrite any previous value(s) of the same component type.
|
||||
///
|
||||
/// You should prefer to use the typed API [`EntityMut::insert`] where possible.
|
||||
/// If your [`Bundle`] only has one component, use the cached API [`EntityMut::insert_by_id`].
|
||||
///
|
||||
/// If possible, pass a sorted slice of `ComponentId` to maximize caching potential.
|
||||
///
|
||||
/// # Safety
|
||||
/// - Each [`ComponentId`] must be from the same world as [`EntityMut`]
|
||||
/// - Each [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
|
||||
pub unsafe fn insert_by_ids<'a, I: Iterator<Item = OwningPtr<'a>>>(
|
||||
&mut self,
|
||||
component_ids: &[ComponentId],
|
||||
iter_components: I,
|
||||
) -> &mut Self {
|
||||
let change_tick = self.world.change_tick();
|
||||
|
||||
let bundles = &mut self.world.bundles;
|
||||
let components = &mut self.world.components;
|
||||
|
||||
let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids);
|
||||
let bundle_inserter = bundle_info.get_bundle_inserter(
|
||||
&mut self.world.entities,
|
||||
&mut self.world.archetypes,
|
||||
&mut self.world.components,
|
||||
&mut self.world.storages,
|
||||
self.location.archetype_id,
|
||||
change_tick,
|
||||
);
|
||||
|
||||
self.location = insert_dynamic_bundle(
|
||||
bundle_inserter,
|
||||
self.entity,
|
||||
self.location,
|
||||
iter_components,
|
||||
storage_types.iter().cloned(),
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes all components in the [`Bundle`] from the entity and returns their previous values.
|
||||
///
|
||||
/// **Note:** If the entity does not have every component in the bundle, this method will not
|
||||
|
@ -311,7 +395,7 @@ impl<'w> EntityMut<'w> {
|
|||
return None;
|
||||
}
|
||||
|
||||
let mut bundle_components = bundle_info.component_ids.iter().cloned();
|
||||
let mut bundle_components = bundle_info.components().iter().cloned();
|
||||
let entity = self.entity;
|
||||
// SAFETY: bundle components are iterated in order, which guarantees that the component type
|
||||
// matches
|
||||
|
@ -463,7 +547,7 @@ impl<'w> EntityMut<'w> {
|
|||
|
||||
let old_archetype = &mut archetypes[old_location.archetype_id];
|
||||
let entity = self.entity;
|
||||
for component_id in bundle_info.component_ids.iter().cloned() {
|
||||
for component_id in bundle_info.components().iter().cloned() {
|
||||
if old_archetype.contains(component_id) {
|
||||
removed_components.send(component_id, entity);
|
||||
|
||||
|
@ -603,7 +687,7 @@ impl<'w> EntityMut<'w> {
|
|||
/// // Mutate the world while we have access to it.
|
||||
/// let mut r = world.resource_mut::<R>();
|
||||
/// r.0 += 1;
|
||||
///
|
||||
///
|
||||
/// // Return a value from the world before giving it back to the `EntityMut`.
|
||||
/// *r
|
||||
/// });
|
||||
|
@ -672,6 +756,44 @@ impl<'w> EntityMut<'w> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Inserts a dynamic [`Bundle`] into the entity.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
|
||||
/// [`BundleInfo`] used to construct [`BundleInserter`]
|
||||
/// - [`Entity`] must correspond to [`EntityLocation`]
|
||||
unsafe fn insert_dynamic_bundle<
|
||||
'a,
|
||||
I: Iterator<Item = OwningPtr<'a>>,
|
||||
S: Iterator<Item = StorageType>,
|
||||
>(
|
||||
mut bundle_inserter: BundleInserter<'_, '_>,
|
||||
entity: Entity,
|
||||
location: EntityLocation,
|
||||
components: I,
|
||||
storage_types: S,
|
||||
) -> EntityLocation {
|
||||
struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> {
|
||||
components: I,
|
||||
}
|
||||
|
||||
impl<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> DynamicBundle
|
||||
for DynamicInsertBundle<'a, I>
|
||||
{
|
||||
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
|
||||
self.components.for_each(|(t, ptr)| func(t, ptr));
|
||||
}
|
||||
}
|
||||
|
||||
let bundle = DynamicInsertBundle {
|
||||
components: storage_types.zip(components),
|
||||
};
|
||||
|
||||
// SAFETY: location matches current entity.
|
||||
unsafe { bundle_inserter.insert(entity, location, bundle) }
|
||||
}
|
||||
|
||||
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the
|
||||
/// removal was invalid). in the event that adding the given bundle does not result in an Archetype
|
||||
/// change. Results are cached in the Archetype Graph to avoid redundant work.
|
||||
|
@ -694,9 +816,11 @@ unsafe fn remove_bundle_from_archetype(
|
|||
let remove_bundle_result = {
|
||||
let current_archetype = &mut archetypes[archetype_id];
|
||||
if intersection {
|
||||
current_archetype.edges().get_remove_bundle(bundle_info.id)
|
||||
current_archetype
|
||||
.edges()
|
||||
.get_remove_bundle(bundle_info.id())
|
||||
} else {
|
||||
current_archetype.edges().get_take_bundle(bundle_info.id)
|
||||
current_archetype.edges().get_take_bundle(bundle_info.id())
|
||||
}
|
||||
};
|
||||
let result = if let Some(result) = remove_bundle_result {
|
||||
|
@ -710,7 +834,7 @@ unsafe fn remove_bundle_from_archetype(
|
|||
let current_archetype = &mut archetypes[archetype_id];
|
||||
let mut removed_table_components = Vec::new();
|
||||
let mut removed_sparse_set_components = Vec::new();
|
||||
for component_id in bundle_info.component_ids.iter().cloned() {
|
||||
for component_id in bundle_info.components().iter().cloned() {
|
||||
if current_archetype.contains(component_id) {
|
||||
// SAFETY: bundle components were already initialized by bundles.get_info
|
||||
let component_info = components.get_info_unchecked(component_id);
|
||||
|
@ -724,7 +848,7 @@ unsafe fn remove_bundle_from_archetype(
|
|||
// graph
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.insert_take_bundle(bundle_info.id, None);
|
||||
.insert_take_bundle(bundle_info.id(), None);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
@ -763,11 +887,11 @@ unsafe fn remove_bundle_from_archetype(
|
|||
if intersection {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.insert_remove_bundle(bundle_info.id, result);
|
||||
.insert_remove_bundle(bundle_info.id(), result);
|
||||
} else {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.insert_take_bundle(bundle_info.id, result);
|
||||
.insert_take_bundle(bundle_info.id(), result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
@ -833,6 +957,7 @@ pub(crate) unsafe fn take_component<'a>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_ptr::OwningPtr;
|
||||
use std::panic::AssertUnwindSafe;
|
||||
|
||||
use crate as bevy_ecs;
|
||||
|
@ -860,9 +985,13 @@ mod tests {
|
|||
assert_eq!(a, vec![1]);
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[derive(Component, Clone, Copy, Debug, PartialEq)]
|
||||
struct TestComponent(u32);
|
||||
|
||||
#[derive(Component, Clone, Copy, Debug, PartialEq)]
|
||||
#[component(storage = "SparseSet")]
|
||||
struct TestComponent2(u32);
|
||||
|
||||
#[test]
|
||||
fn entity_ref_get_by_id() {
|
||||
let mut world = World::new();
|
||||
|
@ -1105,4 +1234,72 @@ mod tests {
|
|||
|
||||
assert_eq!(world.entity(e2).get::<Dense>().unwrap(), &Dense(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_mut_insert_by_id() {
|
||||
let mut world = World::new();
|
||||
let test_component_id = world.init_component::<TestComponent>();
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
OwningPtr::make(TestComponent(42), |ptr| {
|
||||
// SAFETY: `ptr` matches the component id
|
||||
unsafe { entity.insert_by_id(test_component_id, ptr) };
|
||||
});
|
||||
|
||||
let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect();
|
||||
|
||||
assert_eq!(components, vec![&TestComponent(42)]);
|
||||
|
||||
// Compare with `insert_bundle_by_id`
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
OwningPtr::make(TestComponent(84), |ptr| {
|
||||
// SAFETY: `ptr` matches the component id
|
||||
unsafe { entity.insert_by_ids(&[test_component_id], vec![ptr].into_iter()) };
|
||||
});
|
||||
|
||||
let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect();
|
||||
|
||||
assert_eq!(components, vec![&TestComponent(42), &TestComponent(84)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn entity_mut_insert_bundle_by_id() {
|
||||
let mut world = World::new();
|
||||
let test_component_id = world.init_component::<TestComponent>();
|
||||
let test_component_2_id = world.init_component::<TestComponent2>();
|
||||
|
||||
let component_ids = [test_component_id, test_component_2_id];
|
||||
let test_component_value = TestComponent(42);
|
||||
let test_component_2_value = TestComponent2(84);
|
||||
|
||||
let mut entity = world.spawn_empty();
|
||||
OwningPtr::make(test_component_value, |ptr1| {
|
||||
OwningPtr::make(test_component_2_value, |ptr2| {
|
||||
// SAFETY: `ptr1` and `ptr2` match the component ids
|
||||
unsafe { entity.insert_by_ids(&component_ids, vec![ptr1, ptr2].into_iter()) };
|
||||
});
|
||||
});
|
||||
|
||||
let dynamic_components: Vec<_> = world
|
||||
.query::<(&TestComponent, &TestComponent2)>()
|
||||
.iter(&world)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
dynamic_components,
|
||||
vec![(&TestComponent(42), &TestComponent2(84))]
|
||||
);
|
||||
|
||||
// Compare with `World` generated using static type equivalents
|
||||
let mut static_world = World::new();
|
||||
|
||||
static_world.spawn((test_component_value, test_component_2_value));
|
||||
let static_components: Vec<_> = static_world
|
||||
.query::<(&TestComponent, &TestComponent2)>()
|
||||
.iter(&static_world)
|
||||
.collect();
|
||||
|
||||
assert_eq!(dynamic_components, static_components);
|
||||
}
|
||||
}
|
||||
|
|
10
crates/bevy_ecs/src/world/error.rs
Normal file
10
crates/bevy_ecs/src/world/error.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::schedule::BoxedScheduleLabel;
|
||||
|
||||
/// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist.
|
||||
///
|
||||
/// [`World::try_run_schedule`]: crate::world::World::try_run_schedule
|
||||
#[derive(Error, Debug)]
|
||||
#[error("The schedule with the label {0:?} was not found.")]
|
||||
pub struct TryRunScheduleError(pub BoxedScheduleLabel);
|
|
@ -1,4 +1,5 @@
|
|||
use crate::{
|
||||
component::Tick,
|
||||
storage::SparseSetIndex,
|
||||
system::{ReadOnlySystemParam, SystemParam},
|
||||
world::{FromWorld, World},
|
||||
|
@ -56,7 +57,7 @@ unsafe impl SystemParam for WorldId {
|
|||
_: &'state mut Self::State,
|
||||
_: &crate::system::SystemMeta,
|
||||
world: &'world super::World,
|
||||
_: u32,
|
||||
_: Tick,
|
||||
) -> Self::Item<'world, 'state> {
|
||||
world.id
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
mod entity_ref;
|
||||
pub mod error;
|
||||
mod spawn_batch;
|
||||
pub mod unsafe_world_cell;
|
||||
mod world_cell;
|
||||
|
@ -12,7 +13,7 @@ use crate::{
|
|||
archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes},
|
||||
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
|
||||
change_detection::{MutUntyped, TicksMut},
|
||||
component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components},
|
||||
component::{Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, Tick},
|
||||
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
|
||||
event::{Event, Events},
|
||||
query::{DebugCheckedUnwrap, QueryState, ReadOnlyWorldQuery, WorldQuery},
|
||||
|
@ -20,6 +21,7 @@ use crate::{
|
|||
schedule::{Schedule, ScheduleLabel, Schedules},
|
||||
storage::{ResourceData, Storages},
|
||||
system::Resource,
|
||||
world::error::TryRunScheduleError,
|
||||
};
|
||||
use bevy_ptr::{OwningPtr, Ptr};
|
||||
use bevy_utils::tracing::warn;
|
||||
|
@ -64,8 +66,8 @@ pub struct World {
|
|||
/// Access cache used by [WorldCell]. Is only accessed in the `Drop` impl of `WorldCell`.
|
||||
pub(crate) archetype_component_access: ArchetypeComponentAccess,
|
||||
pub(crate) change_tick: AtomicU32,
|
||||
pub(crate) last_change_tick: u32,
|
||||
pub(crate) last_check_tick: u32,
|
||||
pub(crate) last_change_tick: Tick,
|
||||
pub(crate) last_check_tick: Tick,
|
||||
}
|
||||
|
||||
impl Default for World {
|
||||
|
@ -82,8 +84,8 @@ impl Default for World {
|
|||
// Default value is `1`, and `last_change_tick`s default to `0`, such that changes
|
||||
// are detected on first system runs and for direct world queries.
|
||||
change_tick: AtomicU32::new(1),
|
||||
last_change_tick: 0,
|
||||
last_check_tick: 0,
|
||||
last_change_tick: Tick::new(0),
|
||||
last_check_tick: Tick::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -493,6 +495,7 @@ impl World {
|
|||
/// ```
|
||||
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityMut {
|
||||
self.flush();
|
||||
let change_tick = self.change_tick();
|
||||
let entity = self.entities.alloc();
|
||||
let entity_location = {
|
||||
let bundle_info = self
|
||||
|
@ -503,7 +506,7 @@ impl World {
|
|||
&mut self.archetypes,
|
||||
&mut self.components,
|
||||
&mut self.storages,
|
||||
*self.change_tick.get_mut(),
|
||||
change_tick,
|
||||
);
|
||||
|
||||
// SAFETY: bundle's type matches `bundle_info`, entity is allocated but non-existent
|
||||
|
@ -1174,8 +1177,7 @@ impl World {
|
|||
{
|
||||
self.flush();
|
||||
|
||||
let iter = iter.into_iter();
|
||||
let change_tick = *self.change_tick.get_mut();
|
||||
let change_tick = self.change_tick();
|
||||
|
||||
let bundle_info = self
|
||||
.bundles
|
||||
|
@ -1306,8 +1308,8 @@ impl World {
|
|||
ticks: TicksMut {
|
||||
added: &mut ticks.added,
|
||||
changed: &mut ticks.changed,
|
||||
last_change_tick,
|
||||
change_tick,
|
||||
last_run: last_change_tick,
|
||||
this_run: change_tick,
|
||||
},
|
||||
};
|
||||
let result = f(self, value_mut);
|
||||
|
@ -1467,9 +1469,11 @@ impl World {
|
|||
}
|
||||
}
|
||||
|
||||
/// Increments the world's current change tick, and returns the old value.
|
||||
#[inline]
|
||||
pub fn increment_change_tick(&self) -> u32 {
|
||||
self.change_tick.fetch_add(1, Ordering::AcqRel)
|
||||
pub fn increment_change_tick(&self) -> Tick {
|
||||
let prev_tick = self.change_tick.fetch_add(1, Ordering::AcqRel);
|
||||
Tick::new(prev_tick)
|
||||
}
|
||||
|
||||
/// Reads the current change tick of this world.
|
||||
|
@ -1477,8 +1481,9 @@ impl World {
|
|||
/// If you have exclusive (`&mut`) access to the world, consider using [`change_tick()`](Self::change_tick),
|
||||
/// which is more efficient since it does not require atomic synchronization.
|
||||
#[inline]
|
||||
pub fn read_change_tick(&self) -> u32 {
|
||||
self.change_tick.load(Ordering::Acquire)
|
||||
pub fn read_change_tick(&self) -> Tick {
|
||||
let tick = self.change_tick.load(Ordering::Acquire);
|
||||
Tick::new(tick)
|
||||
}
|
||||
|
||||
/// Reads the current change tick of this world.
|
||||
|
@ -1486,12 +1491,13 @@ impl World {
|
|||
/// This does the same thing as [`read_change_tick()`](Self::read_change_tick), only this method
|
||||
/// is more efficient since it does not require atomic synchronization.
|
||||
#[inline]
|
||||
pub fn change_tick(&mut self) -> u32 {
|
||||
*self.change_tick.get_mut()
|
||||
pub fn change_tick(&mut self) -> Tick {
|
||||
let tick = *self.change_tick.get_mut();
|
||||
Tick::new(tick)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn last_change_tick(&self) -> u32 {
|
||||
pub fn last_change_tick(&self) -> Tick {
|
||||
self.last_change_tick
|
||||
}
|
||||
|
||||
|
@ -1503,7 +1509,7 @@ impl World {
|
|||
// TODO: benchmark and optimize
|
||||
pub fn check_change_ticks(&mut self) {
|
||||
let change_tick = self.change_tick();
|
||||
if change_tick.wrapping_sub(self.last_check_tick) < CHECK_TICK_THRESHOLD {
|
||||
if change_tick.relative_to(self.last_check_tick).get() < CHECK_TICK_THRESHOLD {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1710,6 +1716,47 @@ impl World {
|
|||
schedules.insert(label, schedule);
|
||||
}
|
||||
|
||||
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
|
||||
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
|
||||
///
|
||||
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
|
||||
/// and system state is cached.
|
||||
///
|
||||
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
|
||||
pub fn try_run_schedule(
|
||||
&mut self,
|
||||
label: impl ScheduleLabel,
|
||||
) -> Result<(), TryRunScheduleError> {
|
||||
self.try_run_schedule_ref(&label)
|
||||
}
|
||||
|
||||
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
|
||||
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
|
||||
///
|
||||
/// Unlike the `try_run_schedule` method, this method takes the label by reference, which can save a clone.
|
||||
///
|
||||
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
|
||||
/// and system state is cached.
|
||||
///
|
||||
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
|
||||
pub fn try_run_schedule_ref(
|
||||
&mut self,
|
||||
label: &dyn ScheduleLabel,
|
||||
) -> Result<(), TryRunScheduleError> {
|
||||
let Some((extracted_label, mut schedule)) = self.resource_mut::<Schedules>().remove_entry(label) else {
|
||||
return Err(TryRunScheduleError(label.dyn_clone()));
|
||||
};
|
||||
|
||||
// TODO: move this span to Schedule::run
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered();
|
||||
schedule.run(self);
|
||||
self.resource_mut::<Schedules>()
|
||||
.insert(extracted_label, schedule);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the [`Schedule`] associated with the `label` a single time.
|
||||
///
|
||||
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
|
||||
|
@ -1737,17 +1784,8 @@ impl World {
|
|||
///
|
||||
/// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added.
|
||||
pub fn run_schedule_ref(&mut self, label: &dyn ScheduleLabel) {
|
||||
let (extracted_label, mut schedule) = self
|
||||
.resource_mut::<Schedules>()
|
||||
.remove_entry(label)
|
||||
.unwrap_or_else(|| panic!("The schedule with the label {label:?} was not found."));
|
||||
|
||||
// TODO: move this span to Schedule::run
|
||||
#[cfg(feature = "trace")]
|
||||
let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered();
|
||||
schedule.run(self);
|
||||
self.resource_mut::<Schedules>()
|
||||
.insert(extracted_label, schedule);
|
||||
self.try_run_schedule_ref(label)
|
||||
.unwrap_or_else(|e| panic!("{}", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ where
|
|||
// necessary
|
||||
world.flush();
|
||||
|
||||
let change_tick = world.change_tick();
|
||||
|
||||
let (lower, upper) = iter.size_hint();
|
||||
let length = upper.unwrap_or(lower);
|
||||
|
||||
|
@ -37,7 +39,7 @@ where
|
|||
&mut world.archetypes,
|
||||
&mut world.components,
|
||||
&mut world.storages,
|
||||
*world.change_tick.get_mut(),
|
||||
change_tick,
|
||||
);
|
||||
spawner.reserve_storage(length);
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
bundle::Bundles,
|
||||
change_detection::{MutUntyped, TicksMut},
|
||||
component::{
|
||||
ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, TickCells,
|
||||
ComponentId, ComponentStorage, ComponentTicks, Components, StorageType, Tick, TickCells,
|
||||
},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
prelude::Component,
|
||||
|
@ -185,16 +185,17 @@ impl<'w> UnsafeWorldCell<'w> {
|
|||
|
||||
/// Reads the current change tick of this world.
|
||||
#[inline]
|
||||
pub fn read_change_tick(self) -> u32 {
|
||||
pub fn read_change_tick(self) -> Tick {
|
||||
// SAFETY:
|
||||
// - we only access world metadata
|
||||
unsafe { self.world_metadata() }
|
||||
let tick = unsafe { self.world_metadata() }
|
||||
.change_tick
|
||||
.load(Ordering::Acquire)
|
||||
.load(Ordering::Acquire);
|
||||
Tick::new(tick)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn last_change_tick(self) -> u32 {
|
||||
pub fn last_change_tick(self) -> Tick {
|
||||
// SAFETY:
|
||||
// - we only access world metadata
|
||||
unsafe { self.world_metadata() }.last_change_tick
|
||||
|
@ -655,8 +656,8 @@ impl<'w> UnsafeEntityCell<'w> {
|
|||
#[inline]
|
||||
pub(crate) unsafe fn get_mut_using_ticks<T: Component>(
|
||||
&self,
|
||||
last_change_tick: u32,
|
||||
change_tick: u32,
|
||||
last_change_tick: Tick,
|
||||
change_tick: Tick,
|
||||
) -> Option<Mut<'w, T>> {
|
||||
let component_id = self.world.components().get_id(TypeId::of::<T>())?;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ note: required by a bound in `_::assert_readonly`
|
|||
--> tests/ui/world_query_derive.rs:7:10
|
||||
|
|
||||
7 | #[derive(WorldQuery)]
|
||||
| ^^^^^^^^^^ required by this bound in `_::assert_readonly`
|
||||
| ^^^^^^^^^^ required by this bound in `assert_readonly`
|
||||
= note: this error originates in the derive macro `WorldQuery` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error[E0277]: the trait bound `MutableMarked: ReadOnlyWorldQuery` is not satisfied
|
||||
|
@ -41,5 +41,5 @@ note: required by a bound in `_::assert_readonly`
|
|||
--> tests/ui/world_query_derive.rs:18:10
|
||||
|
|
||||
18 | #[derive(WorldQuery)]
|
||||
| ^^^^^^^^^^ required by this bound in `_::assert_readonly`
|
||||
| ^^^^^^^^^^ required by this bound in `assert_readonly`
|
||||
= note: this error originates in the derive macro `WorldQuery` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
mod converter;
|
||||
mod gilrs_system;
|
||||
|
||||
use bevy_app::{App, CoreSet, Plugin, StartupSet};
|
||||
use bevy_app::{App, Plugin, PreStartup, PreUpdate};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_input::InputSystem;
|
||||
use bevy_utils::tracing::error;
|
||||
|
@ -20,14 +20,8 @@ impl Plugin for GilrsPlugin {
|
|||
{
|
||||
Ok(gilrs) => {
|
||||
app.insert_non_send_resource(gilrs)
|
||||
.add_startup_system(
|
||||
gilrs_event_startup_system.in_base_set(StartupSet::PreStartup),
|
||||
)
|
||||
.add_system(
|
||||
gilrs_event_system
|
||||
.before(InputSystem)
|
||||
.in_base_set(CoreSet::PreUpdate),
|
||||
);
|
||||
.add_systems(PreStartup, gilrs_event_startup_system)
|
||||
.add_systems(PreUpdate, gilrs_event_system.before(InputSystem));
|
||||
}
|
||||
Err(err) => error!("Failed to start Gilrs. {}", err),
|
||||
}
|
||||
|
|
23
crates/bevy_gizmos/Cargo.toml
Normal file
23
crates/bevy_gizmos/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "bevy_gizmos"
|
||||
version = "0.11.0-dev"
|
||||
edition = "2021"
|
||||
description = "Provides gizmos for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# Bevy
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.11.0-dev", optional = true }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.11.0-dev", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.11.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.11.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.11.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.11.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.11.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.11.0-dev" }
|
||||
bevy_core = { path = "../bevy_core", version = "0.11.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.11.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.11.0-dev" }
|
349
crates/bevy_gizmos/src/gizmos.rs
Normal file
349
crates/bevy_gizmos/src/gizmos.rs
Normal file
|
@ -0,0 +1,349 @@
|
|||
use std::{f32::consts::TAU, iter};
|
||||
|
||||
use bevy_ecs::{
|
||||
system::{Deferred, Resource, SystemBuffer, SystemMeta},
|
||||
world::World,
|
||||
};
|
||||
use bevy_math::{Mat2, Quat, Vec2, Vec3};
|
||||
use bevy_render::prelude::Color;
|
||||
|
||||
type PositionItem = [f32; 3];
|
||||
type ColorItem = [f32; 4];
|
||||
|
||||
const DEFAULT_CIRCLE_SEGMENTS: usize = 32;
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub(crate) struct GizmoStorage {
|
||||
pub list_positions: Vec<PositionItem>,
|
||||
pub list_colors: Vec<ColorItem>,
|
||||
pub strip_positions: Vec<PositionItem>,
|
||||
pub strip_colors: Vec<ColorItem>,
|
||||
}
|
||||
|
||||
pub type Gizmos<'s> = Deferred<'s, GizmoBuffer>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct GizmoBuffer {
|
||||
list_positions: Vec<PositionItem>,
|
||||
list_colors: Vec<ColorItem>,
|
||||
strip_positions: Vec<PositionItem>,
|
||||
strip_colors: Vec<ColorItem>,
|
||||
}
|
||||
|
||||
impl SystemBuffer for GizmoBuffer {
|
||||
fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) {
|
||||
let mut storage = world.resource_mut::<GizmoStorage>();
|
||||
storage.list_positions.append(&mut self.list_positions);
|
||||
storage.list_colors.append(&mut self.list_colors);
|
||||
storage.strip_positions.append(&mut self.strip_positions);
|
||||
storage.strip_colors.append(&mut self.strip_colors);
|
||||
}
|
||||
}
|
||||
|
||||
impl GizmoBuffer {
|
||||
#[inline]
|
||||
pub fn line(&mut self, start: Vec3, end: Vec3, color: Color) {
|
||||
self.extend_list_positions([start, end]);
|
||||
self.add_list_color(color, 2);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
#[inline]
|
||||
pub fn line_gradient(&mut self, start: Vec3, end: Vec3, start_color: Color, end_color: Color) {
|
||||
self.extend_list_positions([start, end]);
|
||||
self.extend_list_colors([start_color, end_color]);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray(&mut self, start: Vec3, vector: Vec3, color: Color) {
|
||||
self.line(start, start + vector, color);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray_gradient(
|
||||
&mut self,
|
||||
start: Vec3,
|
||||
vector: Vec3,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
self.line_gradient(start, start + vector, start_color, end_color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip(&mut self, positions: impl IntoIterator<Item = Vec3>, color: Color) {
|
||||
self.extend_strip_positions(positions.into_iter());
|
||||
self.strip_colors
|
||||
.resize(self.strip_positions.len() - 1, color.as_linear_rgba_f32());
|
||||
self.strip_colors.push([f32::NAN; 4]);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip_gradient(&mut self, points: impl IntoIterator<Item = (Vec3, Color)>) {
|
||||
let points = points.into_iter();
|
||||
|
||||
let (min, _) = points.size_hint();
|
||||
self.strip_positions.reserve(min);
|
||||
self.strip_colors.reserve(min);
|
||||
|
||||
for (position, color) in points {
|
||||
self.strip_positions.push(position.to_array());
|
||||
self.strip_colors.push(color.as_linear_rgba_f32());
|
||||
}
|
||||
|
||||
self.strip_positions.push([f32::NAN; 3]);
|
||||
self.strip_colors.push([f32::NAN; 4]);
|
||||
}
|
||||
|
||||
/// Draw a circle at `position` with the flat side facing `normal`.
|
||||
#[inline]
|
||||
pub fn circle(
|
||||
&mut self,
|
||||
position: Vec3,
|
||||
normal: Vec3,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
) -> CircleBuilder {
|
||||
CircleBuilder {
|
||||
buffer: self,
|
||||
position,
|
||||
normal,
|
||||
radius,
|
||||
color,
|
||||
segments: DEFAULT_CIRCLE_SEGMENTS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a sphere.
|
||||
#[inline]
|
||||
pub fn sphere(
|
||||
&mut self,
|
||||
position: Vec3,
|
||||
rotation: Quat,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
) -> SphereBuilder {
|
||||
SphereBuilder {
|
||||
buffer: self,
|
||||
position,
|
||||
rotation,
|
||||
radius,
|
||||
color,
|
||||
circle_segments: DEFAULT_CIRCLE_SEGMENTS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a rectangle.
|
||||
#[inline]
|
||||
pub fn rect(&mut self, position: Vec3, rotation: Quat, size: Vec2, color: Color) {
|
||||
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2.extend(0.));
|
||||
self.linestrip([tl, tr, br, bl, tl], color);
|
||||
}
|
||||
|
||||
/// Draw a box.
|
||||
#[inline]
|
||||
pub fn cuboid(&mut self, position: Vec3, rotation: Quat, size: Vec3, color: Color) {
|
||||
let rect = rect_inner(size.truncate());
|
||||
// Front
|
||||
let [tlf, trf, brf, blf] = rect.map(|vec2| position + rotation * vec2.extend(size.z / 2.));
|
||||
// Back
|
||||
let [tlb, trb, brb, blb] = rect.map(|vec2| position + rotation * vec2.extend(-size.z / 2.));
|
||||
|
||||
let positions = [
|
||||
tlf, trf, trf, brf, brf, blf, blf, tlf, // Front
|
||||
tlb, trb, trb, brb, brb, blb, blb, tlb, // Back
|
||||
tlf, tlb, trf, trb, brf, brb, blf, blb, // Front to back
|
||||
];
|
||||
self.extend_list_positions(positions);
|
||||
self.add_list_color(color, 24);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
#[inline]
|
||||
pub fn line_2d(&mut self, start: Vec2, end: Vec2, color: Color) {
|
||||
self.line(start.extend(0.), end.extend(0.), color);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `end`.
|
||||
#[inline]
|
||||
pub fn line_gradient_2d(
|
||||
&mut self,
|
||||
start: Vec2,
|
||||
end: Vec2,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
self.line_gradient(start.extend(0.), end.extend(0.), start_color, end_color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip_2d(&mut self, positions: impl IntoIterator<Item = Vec2>, color: Color) {
|
||||
self.linestrip(positions.into_iter().map(|vec2| vec2.extend(0.)), color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn linestrip_gradient_2d(&mut self, positions: impl IntoIterator<Item = (Vec2, Color)>) {
|
||||
self.linestrip_gradient(
|
||||
positions
|
||||
.into_iter()
|
||||
.map(|(vec2, color)| (vec2.extend(0.), color)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray_2d(&mut self, start: Vec2, vector: Vec2, color: Color) {
|
||||
self.line_2d(start, start + vector, color);
|
||||
}
|
||||
|
||||
/// Draw a line from `start` to `start + vector`.
|
||||
#[inline]
|
||||
pub fn ray_gradient_2d(
|
||||
&mut self,
|
||||
start: Vec2,
|
||||
vector: Vec2,
|
||||
start_color: Color,
|
||||
end_color: Color,
|
||||
) {
|
||||
self.line_gradient_2d(start, start + vector, start_color, end_color);
|
||||
}
|
||||
|
||||
// Draw a circle.
|
||||
#[inline]
|
||||
pub fn circle_2d(&mut self, position: Vec2, radius: f32, color: Color) -> Circle2dBuilder {
|
||||
Circle2dBuilder {
|
||||
buffer: self,
|
||||
position,
|
||||
radius,
|
||||
color,
|
||||
segments: DEFAULT_CIRCLE_SEGMENTS,
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw a rectangle.
|
||||
#[inline]
|
||||
pub fn rect_2d(&mut self, position: Vec2, rotation: f32, size: Vec2, color: Color) {
|
||||
let rotation = Mat2::from_angle(rotation);
|
||||
let [tl, tr, br, bl] = rect_inner(size).map(|vec2| position + rotation * vec2);
|
||||
self.linestrip_2d([tl, tr, br, bl, tl], color);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extend_list_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
|
||||
self.list_positions
|
||||
.extend(positions.into_iter().map(|vec3| vec3.to_array()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extend_list_colors(&mut self, colors: impl IntoIterator<Item = Color>) {
|
||||
self.list_colors
|
||||
.extend(colors.into_iter().map(|color| color.as_linear_rgba_f32()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_list_color(&mut self, color: Color, count: usize) {
|
||||
self.list_colors
|
||||
.extend(iter::repeat(color.as_linear_rgba_f32()).take(count));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn extend_strip_positions(&mut self, positions: impl IntoIterator<Item = Vec3>) {
|
||||
self.strip_positions.extend(
|
||||
positions
|
||||
.into_iter()
|
||||
.map(|vec3| vec3.to_array())
|
||||
.chain(iter::once([f32::NAN; 3])),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CircleBuilder<'a> {
|
||||
buffer: &'a mut GizmoBuffer,
|
||||
position: Vec3,
|
||||
normal: Vec3,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
segments: usize,
|
||||
}
|
||||
|
||||
impl<'a> CircleBuilder<'a> {
|
||||
pub fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for CircleBuilder<'a> {
|
||||
fn drop(&mut self) {
|
||||
let rotation = Quat::from_rotation_arc(Vec3::Z, self.normal);
|
||||
let positions = circle_inner(self.radius, self.segments)
|
||||
.map(|vec2| (self.position + rotation * vec2.extend(0.)));
|
||||
self.buffer.linestrip(positions, self.color);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SphereBuilder<'a> {
|
||||
buffer: &'a mut GizmoBuffer,
|
||||
position: Vec3,
|
||||
rotation: Quat,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
circle_segments: usize,
|
||||
}
|
||||
|
||||
impl SphereBuilder<'_> {
|
||||
pub fn circle_segments(mut self, segments: usize) -> Self {
|
||||
self.circle_segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for SphereBuilder<'_> {
|
||||
fn drop(&mut self) {
|
||||
for axis in Vec3::AXES {
|
||||
self.buffer
|
||||
.circle(self.position, self.rotation * axis, self.radius, self.color)
|
||||
.segments(self.circle_segments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Circle2dBuilder<'a> {
|
||||
buffer: &'a mut GizmoBuffer,
|
||||
position: Vec2,
|
||||
radius: f32,
|
||||
color: Color,
|
||||
segments: usize,
|
||||
}
|
||||
|
||||
impl Circle2dBuilder<'_> {
|
||||
pub fn segments(mut self, segments: usize) -> Self {
|
||||
self.segments = segments;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Circle2dBuilder<'_> {
|
||||
fn drop(&mut self) {
|
||||
let positions = circle_inner(self.radius, self.segments).map(|vec2| (vec2 + self.position));
|
||||
self.buffer.linestrip_2d(positions, self.color);
|
||||
}
|
||||
}
|
||||
|
||||
fn circle_inner(radius: f32, segments: usize) -> impl Iterator<Item = Vec2> {
|
||||
(0..segments + 1).map(move |i| {
|
||||
let angle = i as f32 * TAU / segments as f32;
|
||||
Vec2::from(angle.sin_cos()) * radius
|
||||
})
|
||||
}
|
||||
|
||||
fn rect_inner(size: Vec2) -> [Vec2; 4] {
|
||||
let half_size = size / 2.;
|
||||
let tl = Vec2::new(-half_size.x, half_size.y);
|
||||
let tr = Vec2::new(half_size.x, half_size.y);
|
||||
let bl = Vec2::new(-half_size.x, -half_size.y);
|
||||
let br = Vec2::new(half_size.x, -half_size.y);
|
||||
[tl, tr, br, bl]
|
||||
}
|
187
crates/bevy_gizmos/src/lib.rs
Normal file
187
crates/bevy_gizmos/src/lib.rs
Normal file
|
@ -0,0 +1,187 @@
|
|||
use std::mem;
|
||||
|
||||
use bevy_app::{Last, Plugin};
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, DetectChanges},
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::Mat4;
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
mesh::Mesh,
|
||||
render_phase::AddRenderCommand,
|
||||
render_resource::{PrimitiveTopology, Shader, SpecializedMeshPipelines},
|
||||
Extract, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||
};
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
use bevy_pbr::MeshUniform;
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
use bevy_sprite::{Mesh2dHandle, Mesh2dUniform};
|
||||
|
||||
pub mod gizmos;
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
mod pipeline_2d;
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
mod pipeline_3d;
|
||||
|
||||
use crate::gizmos::GizmoStorage;
|
||||
|
||||
/// The `bevy_gizmos` prelude.
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{gizmos::Gizmos, GizmoConfig};
|
||||
}
|
||||
|
||||
const LINE_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 7414812689238026784);
|
||||
|
||||
pub struct GizmoPlugin;
|
||||
|
||||
impl Plugin for GizmoPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
load_internal_asset!(app, LINE_SHADER_HANDLE, "lines.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.init_resource::<MeshHandles>()
|
||||
.init_resource::<GizmoConfig>()
|
||||
.init_resource::<GizmoStorage>()
|
||||
.add_systems(Last, update_gizmo_meshes);
|
||||
|
||||
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else { return; };
|
||||
|
||||
render_app.add_systems(ExtractSchedule, extract_gizmo_data);
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
{
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
use pipeline_2d::*;
|
||||
|
||||
render_app
|
||||
.add_render_command::<Transparent2d, DrawGizmoLines>()
|
||||
.init_resource::<GizmoLinePipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoLinePipeline>>()
|
||||
.add_systems(Render, queue_gizmos_2d.in_set(RenderSet::Queue));
|
||||
}
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
{
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use pipeline_3d::*;
|
||||
|
||||
render_app
|
||||
.add_render_command::<Opaque3d, DrawGizmoLines>()
|
||||
.init_resource::<GizmoPipeline>()
|
||||
.init_resource::<SpecializedMeshPipelines<GizmoPipeline>>()
|
||||
.add_systems(Render, queue_gizmos_3d.in_set(RenderSet::Queue));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone, Copy)]
|
||||
pub struct GizmoConfig {
|
||||
/// Set to `false` to stop drawing gizmos.
|
||||
///
|
||||
/// Defaults to `true`.
|
||||
pub enabled: bool,
|
||||
/// Draw gizmos on top of everything else, ignoring depth.
|
||||
///
|
||||
/// This setting only affects 3D. In 2D, gizmos are always drawn on top.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
pub on_top: bool,
|
||||
}
|
||||
|
||||
impl Default for GizmoConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
on_top: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct MeshHandles {
|
||||
list: Handle<Mesh>,
|
||||
strip: Handle<Mesh>,
|
||||
}
|
||||
|
||||
impl FromWorld for MeshHandles {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let mut meshes = world.resource_mut::<Assets<Mesh>>();
|
||||
|
||||
MeshHandles {
|
||||
list: meshes.add(Mesh::new(PrimitiveTopology::LineList)),
|
||||
strip: meshes.add(Mesh::new(PrimitiveTopology::LineStrip)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct GizmoMesh;
|
||||
|
||||
fn update_gizmo_meshes(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
handles: Res<MeshHandles>,
|
||||
mut storage: ResMut<GizmoStorage>,
|
||||
) {
|
||||
let list_mesh = meshes.get_mut(&handles.list).unwrap();
|
||||
|
||||
let positions = mem::take(&mut storage.list_positions);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
let colors = mem::take(&mut storage.list_colors);
|
||||
list_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
|
||||
let strip_mesh = meshes.get_mut(&handles.strip).unwrap();
|
||||
|
||||
let positions = mem::take(&mut storage.strip_positions);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||
|
||||
let colors = mem::take(&mut storage.strip_colors);
|
||||
strip_mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, colors);
|
||||
}
|
||||
|
||||
fn extract_gizmo_data(
|
||||
mut commands: Commands,
|
||||
handles: Extract<Res<MeshHandles>>,
|
||||
config: Extract<Res<GizmoConfig>>,
|
||||
) {
|
||||
if config.is_changed() {
|
||||
commands.insert_resource(**config);
|
||||
}
|
||||
|
||||
if !config.enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
let transform = Mat4::IDENTITY;
|
||||
let inverse_transpose_model = transform.inverse().transpose();
|
||||
commands.spawn_batch([&handles.list, &handles.strip].map(|handle| {
|
||||
(
|
||||
GizmoMesh,
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
(
|
||||
handle.clone(),
|
||||
MeshUniform {
|
||||
flags: 0,
|
||||
transform,
|
||||
inverse_transpose_model,
|
||||
},
|
||||
),
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
(
|
||||
Mesh2dHandle(handle.clone()),
|
||||
Mesh2dUniform {
|
||||
flags: 0,
|
||||
transform,
|
||||
inverse_transpose_model,
|
||||
},
|
||||
),
|
||||
)
|
||||
}));
|
||||
}
|
44
crates/bevy_gizmos/src/lines.wgsl
Normal file
44
crates/bevy_gizmos/src/lines.wgsl
Normal file
|
@ -0,0 +1,44 @@
|
|||
#ifdef GIZMO_LINES_3D
|
||||
#import bevy_pbr::mesh_view_bindings
|
||||
#else
|
||||
#import bevy_sprite::mesh2d_view_bindings
|
||||
#endif
|
||||
|
||||
struct VertexInput {
|
||||
@location(0) pos: vec3<f32>,
|
||||
@location(1) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) pos: vec4<f32>,
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
|
||||
struct FragmentOutput {
|
||||
@builtin(frag_depth) depth: f32,
|
||||
@location(0) color: vec4<f32>,
|
||||
}
|
||||
|
||||
@vertex
|
||||
fn vertex(in: VertexInput) -> VertexOutput {
|
||||
var out: VertexOutput;
|
||||
|
||||
out.pos = view.view_proj * vec4<f32>(in.pos, 1.0);
|
||||
out.color = in.color;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) -> FragmentOutput {
|
||||
var out: FragmentOutput;
|
||||
|
||||
#ifdef DEPTH_TEST
|
||||
out.depth = in.pos.z;
|
||||
#else
|
||||
out.depth = 1.0;
|
||||
#endif
|
||||
|
||||
out.color = in.color;
|
||||
return out;
|
||||
}
|
128
crates/bevy_gizmos/src/pipeline_2d.rs
Normal file
128
crates/bevy_gizmos/src/pipeline_2d.rs
Normal file
|
@ -0,0 +1,128 @@
|
|||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_2d::Transparent2d;
|
||||
use bevy_ecs::{
|
||||
prelude::Entity,
|
||||
query::With,
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_render::{
|
||||
mesh::{Mesh, MeshVertexBufferLayout},
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
};
|
||||
use bevy_sprite::*;
|
||||
use bevy_utils::FloatOrd;
|
||||
|
||||
use crate::{GizmoMesh, LINE_SHADER_HANDLE};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GizmoLinePipeline {
|
||||
mesh_pipeline: Mesh2dPipeline,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for GizmoLinePipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
GizmoLinePipeline {
|
||||
mesh_pipeline: render_world.resource::<Mesh2dPipeline>().clone(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for GizmoLinePipeline {
|
||||
type Key = Mesh2dPipelineKey;
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
key: Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
|
||||
let format = if key.contains(Mesh2dPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
};
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: vec![],
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs: vec![],
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: Some(BlendState::ALPHA_BLENDING),
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
layout: vec![self.mesh_pipeline.view_layout.clone()],
|
||||
primitive: PrimitiveState {
|
||||
topology: key.primitive_topology(),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
push_constant_ranges: vec![],
|
||||
label: Some("gizmo_2d_pipeline".into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawGizmoLines = (
|
||||
SetItemPipeline,
|
||||
SetMesh2dViewBindGroup<0>,
|
||||
SetMesh2dBindGroup<1>,
|
||||
DrawMesh2d,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_gizmos_2d(
|
||||
draw_functions: Res<DrawFunctions<Transparent2d>>,
|
||||
pipeline: Res<GizmoLinePipeline>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut specialized_pipelines: ResMut<SpecializedMeshPipelines<GizmoLinePipeline>>,
|
||||
gpu_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
mesh_handles: Query<(Entity, &Mesh2dHandle), With<GizmoMesh>>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Transparent2d>)>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
|
||||
let key = Mesh2dPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for (view, mut phase) in &mut views {
|
||||
let key = key | Mesh2dPipelineKey::from_hdr(view.hdr);
|
||||
for (entity, mesh_handle) in &mesh_handles {
|
||||
let Some(mesh) = gpu_meshes.get(&mesh_handle.0) else { continue; };
|
||||
|
||||
let key = key | Mesh2dPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = specialized_pipelines
|
||||
.specialize(&pipeline_cache, &pipeline, key, &mesh.layout)
|
||||
.unwrap();
|
||||
phase.add(Transparent2d {
|
||||
entity,
|
||||
draw_function,
|
||||
pipeline,
|
||||
sort_key: FloatOrd(f32::MAX),
|
||||
batch_range: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
164
crates/bevy_gizmos/src/pipeline_3d.rs
Normal file
164
crates/bevy_gizmos/src/pipeline_3d.rs
Normal file
|
@ -0,0 +1,164 @@
|
|||
use bevy_asset::Handle;
|
||||
use bevy_core_pipeline::core_3d::Opaque3d;
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
query::With,
|
||||
system::{Query, Res, ResMut, Resource},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_pbr::*;
|
||||
use bevy_render::{
|
||||
mesh::Mesh,
|
||||
render_resource::Shader,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
};
|
||||
use bevy_render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_asset::RenderAssets,
|
||||
render_phase::{DrawFunctions, RenderPhase, SetItemPipeline},
|
||||
render_resource::*,
|
||||
texture::BevyDefault,
|
||||
view::Msaa,
|
||||
};
|
||||
|
||||
use crate::{GizmoConfig, GizmoMesh, LINE_SHADER_HANDLE};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct GizmoPipeline {
|
||||
mesh_pipeline: MeshPipeline,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for GizmoPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
GizmoPipeline {
|
||||
mesh_pipeline: render_world.resource::<MeshPipeline>().clone(),
|
||||
shader: LINE_SHADER_HANDLE.typed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SpecializedMeshPipeline for GizmoPipeline {
|
||||
type Key = (bool, MeshPipelineKey);
|
||||
|
||||
fn specialize(
|
||||
&self,
|
||||
(depth_test, key): Self::Key,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
|
||||
let mut shader_defs = Vec::new();
|
||||
shader_defs.push("GIZMO_LINES_3D".into());
|
||||
shader_defs.push(ShaderDefVal::Int(
|
||||
"MAX_DIRECTIONAL_LIGHTS".to_string(),
|
||||
MAX_DIRECTIONAL_LIGHTS as i32,
|
||||
));
|
||||
shader_defs.push(ShaderDefVal::Int(
|
||||
"MAX_CASCADES_PER_LIGHT".to_string(),
|
||||
MAX_CASCADES_PER_LIGHT as i32,
|
||||
));
|
||||
if depth_test {
|
||||
shader_defs.push("DEPTH_TEST".into());
|
||||
}
|
||||
|
||||
let vertex_buffer_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_COLOR.at_shader_location(1),
|
||||
])?;
|
||||
let bind_group_layout = match key.msaa_samples() {
|
||||
1 => vec![self.mesh_pipeline.view_layout.clone()],
|
||||
_ => {
|
||||
shader_defs.push("MULTISAMPLED".into());
|
||||
vec![self.mesh_pipeline.view_layout_multisampled.clone()]
|
||||
}
|
||||
};
|
||||
|
||||
let format = if key.contains(MeshPipelineKey::HDR) {
|
||||
ViewTarget::TEXTURE_FORMAT_HDR
|
||||
} else {
|
||||
TextureFormat::bevy_default()
|
||||
};
|
||||
|
||||
Ok(RenderPipelineDescriptor {
|
||||
vertex: VertexState {
|
||||
shader: self.shader.clone_weak(),
|
||||
entry_point: "vertex".into(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
buffers: vec![vertex_buffer_layout],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: self.shader.clone_weak(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
layout: bind_group_layout,
|
||||
primitive: PrimitiveState {
|
||||
topology: key.primitive_topology(),
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Greater,
|
||||
stencil: Default::default(),
|
||||
bias: Default::default(),
|
||||
}),
|
||||
multisample: MultisampleState {
|
||||
count: key.msaa_samples(),
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
push_constant_ranges: vec![],
|
||||
label: Some("gizmo_3d_pipeline".into()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type DrawGizmoLines = (
|
||||
SetItemPipeline,
|
||||
SetMeshViewBindGroup<0>,
|
||||
SetMeshBindGroup<1>,
|
||||
DrawMesh,
|
||||
);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn queue_gizmos_3d(
|
||||
draw_functions: Res<DrawFunctions<Opaque3d>>,
|
||||
pipeline: Res<GizmoPipeline>,
|
||||
mut pipelines: ResMut<SpecializedMeshPipelines<GizmoPipeline>>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
render_meshes: Res<RenderAssets<Mesh>>,
|
||||
msaa: Res<Msaa>,
|
||||
mesh_handles: Query<(Entity, &Handle<Mesh>), With<GizmoMesh>>,
|
||||
config: Res<GizmoConfig>,
|
||||
mut views: Query<(&ExtractedView, &mut RenderPhase<Opaque3d>)>,
|
||||
) {
|
||||
let draw_function = draw_functions.read().get_id::<DrawGizmoLines>().unwrap();
|
||||
let key = MeshPipelineKey::from_msaa_samples(msaa.samples());
|
||||
for (view, mut phase) in &mut views {
|
||||
let key = key | MeshPipelineKey::from_hdr(view.hdr);
|
||||
for (entity, mesh_handle) in &mesh_handles {
|
||||
if let Some(mesh) = render_meshes.get(mesh_handle) {
|
||||
let key = key | MeshPipelineKey::from_primitive_topology(mesh.primitive_topology);
|
||||
let pipeline = pipelines
|
||||
.specialize(
|
||||
&pipeline_cache,
|
||||
&pipeline,
|
||||
(!config.on_top, key),
|
||||
&mesh.layout,
|
||||
)
|
||||
.unwrap();
|
||||
phase.add(Opaque3d {
|
||||
entity,
|
||||
pipeline,
|
||||
draw_function,
|
||||
distance: 0.,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue