Merge branch 'main' into transmission

This commit is contained in:
Marco Buono 2023-03-24 22:04:35 -03:00
commit 93377bc7c8
354 changed files with 7941 additions and 4926 deletions

View file

@ -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:

View file

@ -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

View file

@ -1,4 +1,9 @@
(
resources: {
"scene::ResourceA": (
score: 2,
),
},
entities: {
0: (
components: {

View 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
);
}

View file

@ -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)

View file

@ -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();

View file

@ -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);

View file

@ -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(

View file

@ -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.

View file

@ -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),
);
}
}

View file

@ -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(
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()
.in_base_set(CoreSet::StateTransitions),
.chain(),
);
for variant in S::variants() {
default_schedule.configure_set(
OnUpdate(variant.clone())
.in_base_set(CoreSet::Update)
.run_if(in_state(variant)),
);
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.")
#[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)
}
self
}
/// 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."))
};
if let Some(schedule) = schedules.get_mut(&schedule) {
schedule.add_systems(systems);
}
crate::InnerConfigs::Granular(systems) => {
for system in systems {
self.add_system(system);
}
}
} 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
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);
}
/// 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);
}
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);
}
}

View file

@ -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
}

View file

@ -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);

View file

@ -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,
};
}
use bevy_ecs::{
schedule::{
apply_system_buffers, IntoSystemConfig, IntoSystemSetConfig, IntoSystemSetConfigs,
Schedule, ScheduleLabel, SystemSet,
main_schedule::{
First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate,
Startup, StateTransition, Update,
},
system::Local,
world::World,
DynamicPlugin, Plugin, PluginGroup,
};
/// 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
}
}

View 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);
}
}

View file

@ -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>();

View file

@ -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

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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());

View file

@ -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,
#[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)]
pub struct LoadAssets;
/// Asset events are generated.
AssetEvents,
}
#[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);
}
}

View file

@ -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>)
}
}

View file

@ -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);
}
}

View file

@ -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,

View file

@ -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.

View file

@ -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

View file

@ -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(
.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,

View file

@ -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,

View file

@ -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(
.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),
)
.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));
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(

View file

@ -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,

View file

@ -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>();

View file

@ -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>();

View file

@ -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,

View file

@ -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),
);
}
}
}

View file

@ -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();

View file

@ -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),
);
}
}
}

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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,
);
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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"

View file

@ -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);

View file

@ -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,
}

View file

@ -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 {

View file

@ -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");

View file

@ -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,10 +380,15 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
};
TokenStream::from(quote! {
#mutable_impl
#mutable_struct
#readonly_impl
#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)]
@ -381,9 +399,10 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
#(#ignored_field_idents: #ignored_field_types,)*
}
/// 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 {}
#mutable_impl
#read_only_impl
};
#[allow(dead_code)]
const _: () = {
@ -412,7 +431,6 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
#(q.#ignored_field_idents;)*
#(q2.#field_idents;)*
#(q2.#ignored_field_idents;)*
}
};
})

View file

@ -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();

View file

@ -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()
}

View file

@ -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,

View file

@ -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
// 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)
.unwrap();
.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
// 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)
.unwrap();
.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
// 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)
.unwrap();
.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) }
}
/// 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;
// 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)),
)
});
// SAFETY: index either exists, or was initialized
let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
(bundle_info, storage_types)
}
/// # Safety
/// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component.
///
/// `component_id` must be valid [`ComponentId`]'s
unsafe fn initialize_bundle(
bundle_type_name: &'static str,
/// # 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>,
id: BundleId,
) -> BundleInfo {
let mut deduped = component_ids.clone();
deduped.sort();
deduped.dedup();
) -> (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();
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 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);
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(", ");
panic!("Bundle {bundle_type_name} has duplicate components: {names}");
}
BundleInfo { id, component_ids }
(id, storage_types)
}

View file

@ -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]

View file

@ -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;
}
}

View file

@ -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
{

View file

@ -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 }));

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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
///

View file

@ -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,

View file

@ -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)]);
}
}

View file

@ -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,
);
}
}

View file

@ -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();

View file

@ -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);

View file

@ -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()
}

View file

@ -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,38 +858,168 @@ 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 {
/// fn my_system(mut counter: ResMut<Counter>) {
/// counter.0 += 1;
/// }
///
/// fn always() -> bool {
/// true
/// }
/// #
/// # fn my_system() { unreachable!() }
///
/// 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())),
);
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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]);
}
}
}

View file

@ -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 {
for system in system_iter {
self.add_system_inner(system).unwrap();
AddSystemsInnerResult {
densely_chained: true,
nodes: Vec::new(),
}
}
}
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 &current_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 &current_result.nodes {
self.dependency.graph.add_edge(
*previous_node,
*current_node,
(),
);
}
}
}
}
fn add_system<M>(&mut self, system: impl IntoSystemConfig<M>) {
self.add_system_inner(system).unwrap();
if ancestor_chained {
nodes_in_scope.append(&mut previous_result.nodes);
}
fn add_system_inner<M>(
&mut self,
system: impl IntoSystemConfig<M>,
) -> Result<NodeId, ScheduleBuildError> {
let SystemConfig {
system,
graph_info,
conditions,
} = system.into_config();
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_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.

View file

@ -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())

View file

@ -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();
}
}

View file

@ -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

View file

@ -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>,
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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>> {

View file

@ -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);

View file

@ -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(),
);

View file

@ -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(),
);

View file

@ -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();
}

View file

@ -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)
}
}
}

View file

@ -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);
}
}

View file

@ -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);
/// 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;
}

View file

@ -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)
/// )

View file

@ -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);
@ -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);
}
}

View 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);

View file

@ -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
}

View file

@ -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));
}
}

View file

@ -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);

View file

@ -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>())?;

View file

@ -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)

View file

@ -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),
}

View 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" }

View 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]
}

View 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,
},
),
)
}));
}

View 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;
}

View 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,
});
}
}
}

View 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