Merge branch 'main' into Remove-entity-reserving/pending/flushing-system

This commit is contained in:
Eagster 2025-06-11 19:54:06 -04:00 committed by GitHub
commit d899b389d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
319 changed files with 6572 additions and 2442 deletions

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: https://bevyengine.org/donate/
custom: https://bevy.org/donate/

View File

@ -10,4 +10,4 @@ assignees: ''
Provide a link to the documentation and describe how it could be improved. In what ways is it incomplete, incorrect, or misleading?
If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and [create a pull request](https://bevyengine.org/learn/contribute/helping-out/writing-docs/) instead.
If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and [create a pull request](https://bevy.org/learn/contribute/helping-out/writing-docs/) instead.

View File

@ -293,7 +293,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.32.0
uses: crate-ci/typos@v1.33.1
- name: Typos info
if: failure()
run: |

View File

@ -82,7 +82,7 @@ jobs:
- name: Finalize documentation
run: |
echo "<meta http-equiv=\"refresh\" content=\"0; url=bevy/index.html\">" > target/doc/index.html
echo "dev-docs.bevyengine.org" > target/doc/CNAME
echo "dev-docs.bevy.org" > target/doc/CNAME
echo $'User-Agent: *\nDisallow: /' > target/doc/robots.txt
rm target/doc/.lock

View File

@ -43,5 +43,5 @@ jobs:
repo: context.repo.repo,
body: `**Welcome**, new contributor!
Please make sure you've read our [contributing guide](https://bevyengine.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨`
Please make sure you've read our [contributing guide](https://bevy.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨`
})

View File

@ -1,4 +1,4 @@
# Contributing to Bevy
If you'd like to help build Bevy, start by reading this
[introduction](https://bevyengine.org/learn/contribute/introduction). Thanks for contributing!
[introduction](https://bevy.org/learn/contribute/introduction). Thanks for contributing!

View File

@ -5,7 +5,7 @@ edition = "2024"
categories = ["game-engines", "graphics", "gui", "rendering"]
description = "A refreshingly simple data-driven game engine and app framework"
exclude = ["assets/", "tools/", ".github/", "crates/", "examples/wasm/assets/"]
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
license = "MIT OR Apache-2.0"
repository = "https://github.com/bevyengine/bevy"
@ -72,6 +72,7 @@ allow_attributes_without_reason = "warn"
[workspace.lints.rust]
missing_docs = "warn"
mismatched_lifetime_syntaxes = "allow"
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
@ -133,6 +134,7 @@ default = [
"bevy_audio",
"bevy_color",
"bevy_core_pipeline",
"bevy_core_widgets",
"bevy_anti_aliasing",
"bevy_gilrs",
"bevy_gizmos",
@ -291,6 +293,9 @@ bevy_log = ["bevy_internal/bevy_log"]
# Enable input focus subsystem
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
# Headless widget collection for Bevy UI.
bevy_core_widgets = ["bevy_internal/bevy_core_widgets"]
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
@ -534,6 +539,9 @@ libm = ["bevy_internal/libm"]
# Enables use of browser APIs. Note this is currently only applicable on `wasm32` architectures.
web = ["bevy_internal/web"]
# Enable hotpatching of Bevy systems
hotpatching = ["bevy_internal/hotpatching"]
[dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
tracing = { version = "0.1", default-features = false, optional = true }
@ -3539,6 +3547,17 @@ description = "Illustrates how to use 9 Slicing for TextureAtlases in UI"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "ui_transform"
path = "examples/ui/ui_transform.rs"
doc-scrape-examples = true
[package.metadata.example.ui_transform]
name = "UI Transform"
description = "An example demonstrating how to translate, rotate and scale UI elements."
category = "UI (User Interface)"
wasm = true
[[example]]
name = "viewport_debug"
path = "examples/ui/viewport_debug.rs"
@ -3922,6 +3941,16 @@ description = "A simple 2D screen shake effect"
category = "Camera"
wasm = true
[[example]]
name = "2d_on_ui"
path = "examples/camera/2d_on_ui.rs"
doc-scrape-examples = true
[package.metadata.example.2d_on_ui]
name = "2D on Bevy UI"
description = "Shows how to render 2D objects on top of Bevy UI"
category = "Camera"
wasm = true
[package.metadata.example.fps_overlay]
name = "FPS overlay"
@ -4401,3 +4430,37 @@ name = "Cooldown"
description = "Example for cooldown on button clicks"
category = "Usage"
wasm = true
[[example]]
name = "hotpatching_systems"
path = "examples/ecs/hotpatching_systems.rs"
doc-scrape-examples = true
required-features = ["hotpatching"]
[package.metadata.example.hotpatching_systems]
name = "Hotpatching Systems"
description = "Demonstrates how to hotpatch systems"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "core_widgets"
path = "examples/ui/core_widgets.rs"
doc-scrape-examples = true
[package.metadata.example.core_widgets]
name = "Core Widgets"
description = "Demonstrates use of core (headless) widgets in Bevy UI"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "core_widgets_observers"
path = "examples/ui/core_widgets_observers.rs"
doc-scrape-examples = true
[package.metadata.example.core_widgets_observers]
name = "Core Widgets (w/Observers)"
description = "Demonstrates use of core (headless) widgets in Bevy UI, with Observers"
category = "UI (User Interface)"
wasm = true

View File

@ -1,4 +1,4 @@
# [![Bevy](assets/branding/bevy_logo_light_dark_and_dimmed.svg)](https://bevyengine.org)
# [![Bevy](assets/branding/bevy_logo_light_dark_and_dimmed.svg)](https://bevy.org)
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy.svg)](https://crates.io/crates/bevy)
@ -13,7 +13,7 @@ Bevy is a refreshingly simple data-driven game engine built in Rust. It is free
## WARNING
Bevy is still in the early stages of development. Important features are missing. Documentation is sparse. A new version of Bevy containing breaking changes to the API is released [approximately once every 3 months](https://bevyengine.org/news/bevy-0-6/#the-train-release-schedule). We provide [migration guides](https://bevyengine.org/learn/migration-guides/), but we can't guarantee migrations will always be easy. Use only if you are willing to work in this environment.
Bevy is still in the early stages of development. Important features are missing. Documentation is sparse. A new version of Bevy containing breaking changes to the API is released [approximately once every 3 months](https://bevy.org/news/bevy-0-6/#the-train-release-schedule). We provide [migration guides](https://bevy.org/learn/migration-guides/), but we can't guarantee migrations will always be easy. Use only if you are willing to work in this environment.
**MSRV:** Bevy relies heavily on improvements in the Rust language and compiler.
As a result, the Minimum Supported Rust Version (MSRV) is generally close to "the latest stable release" of Rust.
@ -29,15 +29,15 @@ As a result, the Minimum Supported Rust Version (MSRV) is generally close to "th
## About
* **[Features](https://bevyengine.org):** A quick overview of Bevy's features.
* **[News](https://bevyengine.org/news/)**: A development blog that covers our progress, plans and shiny new features.
* **[Features](https://bevy.org):** A quick overview of Bevy's features.
* **[News](https://bevy.org/news/)**: A development blog that covers our progress, plans and shiny new features.
## Docs
* **[Quick Start Guide](https://bevyengine.org/learn/quick-start/introduction):** Bevy's official Quick Start Guide. The best place to start learning Bevy.
* **[Quick Start Guide](https://bevy.org/learn/quick-start/introduction):** Bevy's official Quick Start Guide. The best place to start learning Bevy.
* **[Bevy Rust API Docs](https://docs.rs/bevy):** Bevy's Rust API docs, which are automatically generated from the doc comments in this repo.
* **[Official Examples](https://github.com/bevyengine/bevy/tree/latest/examples):** Bevy's dedicated, runnable examples, which are great for digging into specific concepts.
* **[Community-Made Learning Resources](https://bevyengine.org/assets/#learning)**: More tutorials, documentation, and examples made by the Bevy community.
* **[Community-Made Learning Resources](https://bevy.org/assets/#learning)**: More tutorials, documentation, and examples made by the Bevy community.
## Community
@ -46,11 +46,11 @@ Before contributing or participating in discussions with the community, you shou
* **[Discord](https://discord.gg/bevy):** Bevy's official discord server.
* **[Reddit](https://reddit.com/r/bevy):** Bevy's official subreddit.
* **[GitHub Discussions](https://github.com/bevyengine/bevy/discussions):** The best place for questions about Bevy, answered right here!
* **[Bevy Assets](https://bevyengine.org/assets/):** A collection of awesome Bevy projects, tools, plugins and learning materials.
* **[Bevy Assets](https://bevy.org/assets/):** A collection of awesome Bevy projects, tools, plugins and learning materials.
### Contributing
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://bevyengine.org/learn/contribute/introduction)**.
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://bevy.org/learn/contribute/introduction)**.
For simple problems, feel free to [open an issue](https://github.com/bevyengine/bevy/issues) or
[PR](https://github.com/bevyengine/bevy/pulls) and tackle it yourself!
@ -58,9 +58,9 @@ For more complex architecture decisions and experimental mad science, please ope
## Getting Started
We recommend checking out the [Quick Start Guide](https://bevyengine.org/learn/quick-start/introduction) for a brief introduction.
We recommend checking out the [Quick Start Guide](https://bevy.org/learn/quick-start/introduction) for a brief introduction.
Follow the [Setup guide](https://bevyengine.org/learn/quick-start/getting-started/setup) to ensure your development environment is set up correctly.
Follow the [Setup guide](https://bevy.org/learn/quick-start/getting-started/setup) to ensure your development environment is set up correctly.
Once set up, you can quickly try out the [examples](https://github.com/bevyengine/bevy/tree/latest/examples) by cloning this repo and running the following commands:
```sh
@ -84,7 +84,7 @@ fn main() {
### Fast Compiles
Bevy can be built just fine using default configuration on stable Rust. However for really fast iterative compiles, you should enable the "fast compiles" setup by [following the instructions here](https://bevyengine.org/learn/quick-start/getting-started/setup).
Bevy can be built just fine using default configuration on stable Rust. However for really fast iterative compiles, you should enable the "fast compiles" setup by [following the instructions here](https://bevy.org/learn/quick-start/getting-started/setup).
## [Bevy Cargo Features][cargo_features]
@ -96,7 +96,7 @@ This [list][cargo_features] outlines the different cargo features supported by B
Bevy is the result of the hard work of many people. A huge thanks to all Bevy contributors, the many open source projects that have come before us, the [Rust gamedev ecosystem](https://arewegameyet.rs/), and the many libraries we build on.
A huge thanks to Bevy's [generous sponsors](https://bevyengine.org). Bevy will always be free and open source, but it isn't free to make. Please consider [sponsoring our work](https://bevyengine.org/donate/) if you like what we're building.
A huge thanks to Bevy's [generous sponsors](https://bevy.org). Bevy will always be free and open source, but it isn't free to make. Please consider [sponsoring our work](https://bevy.org/donate/) if you like what we're building.
<!-- This next line need to stay exactly as is. It is required for BrowserStack sponsorship. -->
This project is tested with BrowserStack.

View File

@ -32,7 +32,7 @@ fn segment_ease(c: &mut Criterion) {
fn curve_position(c: &mut Criterion) {
/// A helper function that benchmarks calling [`CubicCurve::position()`] over a generic [`VectorSpace`].
fn bench_curve<M: Measurement, P: VectorSpace>(
fn bench_curve<M: Measurement, P: VectorSpace<Scalar = f32>>(
group: &mut BenchmarkGroup<M>,
name: &str,
curve: CubicCurve<P>,

View File

@ -3,7 +3,7 @@ name = "bevy_a11y"
version = "0.16.0-dev"
edition = "2024"
description = "Provides accessibility support for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy", "accessibility", "a11y"]

View File

@ -1,8 +1,8 @@
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#![no_std]

View File

@ -3,7 +3,7 @@ name = "bevy_animation"
version = "0.16.0-dev"
edition = "2024"
description = "Provides animation functionality for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -55,7 +55,7 @@ pub struct CubicKeyframeCurve<T> {
impl<V> Curve<V> for CubicKeyframeCurve<V>
where
V: VectorSpace,
V: VectorSpace<Scalar = f32>,
{
#[inline]
fn domain(&self) -> Interval {
@ -179,7 +179,7 @@ pub struct WideLinearKeyframeCurve<T> {
impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>
where
T: VectorSpace,
T: VectorSpace<Scalar = f32>,
{
#[inline]
fn domain(&self) -> Interval {
@ -289,7 +289,7 @@ pub struct WideCubicKeyframeCurve<T> {
impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>
where
T: VectorSpace,
T: VectorSpace<Scalar = f32>,
{
#[inline]
fn domain(&self) -> Interval {
@ -406,7 +406,7 @@ fn cubic_spline_interpolation<T>(
step_duration: f32,
) -> T
where
T: VectorSpace,
T: VectorSpace<Scalar = f32>,
{
let coeffs = (vec4(2.0, 1.0, -2.0, 1.0) * lerp + vec4(-3.0, -2.0, 3.0, -1.0)) * lerp;
value_start * (coeffs.x * lerp + 1.0)
@ -415,7 +415,7 @@ where
+ tangent_in_end * step_duration * lerp * coeffs.w
}
fn cubic_spline_interpolate_slices<'a, T: VectorSpace>(
fn cubic_spline_interpolate_slices<'a, T: VectorSpace<Scalar = f32>>(
width: usize,
first: &'a [T],
second: &'a [T],

View File

@ -1,8 +1,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
//! Animation for the game engine Bevy

View File

@ -3,7 +3,7 @@ name = "bevy_anti_aliasing"
version = "0.16.0-dev"
edition = "2024"
description = "Provides various anti aliasing implementations for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -1,9 +0,0 @@
//! Experimental rendering features.
//!
//! Experimental features are features with known problems, missing features,
//! compatibility issues, low performance, and/or future breaking changes, but
//! are included nonetheless for testing purposes.
pub mod taa {
pub use crate::taa::{TemporalAntiAliasNode, TemporalAntiAliasPlugin, TemporalAntiAliasing};
}

View File

@ -2,26 +2,25 @@
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
use bevy_app::Plugin;
use contrast_adaptive_sharpening::CasPlugin;
use fxaa::FxaaPlugin;
use smaa::SmaaPlugin;
use taa::TemporalAntiAliasPlugin;
pub mod contrast_adaptive_sharpening;
pub mod experimental;
pub mod fxaa;
pub mod smaa;
mod taa;
pub mod taa;
#[derive(Default)]
pub struct AntiAliasingPlugin;
impl Plugin for AntiAliasingPlugin {
fn build(&self, app: &mut bevy_app::App) {
app.add_plugins((FxaaPlugin, CasPlugin, SmaaPlugin));
app.add_plugins((FxaaPlugin, SmaaPlugin, TemporalAntiAliasPlugin, CasPlugin));
}
}

View File

@ -62,7 +62,7 @@ impl Plugin for TemporalAntiAliasPlugin {
.add_systems(
Render,
(
prepare_taa_jitter_and_mip_bias.in_set(RenderSystems::ManageViews),
prepare_taa_jitter.in_set(RenderSystems::ManageViews),
prepare_taa_pipelines.in_set(RenderSystems::Prepare),
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
),
@ -113,7 +113,6 @@ impl Plugin for TemporalAntiAliasPlugin {
///
/// # Usage Notes
///
/// The [`TemporalAntiAliasPlugin`] must be added to your app.
/// Any camera with this component must also disable [`Msaa`] by setting it to [`Msaa::Off`].
///
/// [Currently](https://github.com/bevyengine/bevy/issues/8423), TAA cannot be used with [`bevy_render::camera::OrthographicProjection`].
@ -126,11 +125,9 @@ impl Plugin for TemporalAntiAliasPlugin {
///
/// 1. Write particle motion vectors to the motion vectors prepass texture
/// 2. Render particles after TAA
///
/// If no [`MipBias`] component is attached to the camera, TAA will add a `MipBias(-1.0)` component.
#[derive(Component, Reflect, Clone)]
#[reflect(Component, Default, Clone)]
#[require(TemporalJitter, DepthPrepass, MotionVectorPrepass)]
#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass)]
#[doc(alias = "Taa")]
pub struct TemporalAntiAliasing {
/// Set to true to delete the saved temporal history (past frames).
@ -345,16 +342,11 @@ impl SpecializedRenderPipeline for TaaPipeline {
}
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
let mut cameras_3d = main_world.query_filtered::<(
let mut cameras_3d = main_world.query::<(
RenderEntity,
&Camera,
&Projection,
&mut TemporalAntiAliasing,
), (
With<Camera3d>,
With<TemporalJitter>,
With<DepthPrepass>,
With<MotionVectorPrepass>,
Option<&mut TemporalAntiAliasing>,
)>();
for (entity, camera, camera_projection, mut taa_settings) in
@ -364,14 +356,12 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld
let mut entity_commands = commands
.get_entity(entity)
.expect("Camera entity wasn't synced.");
if camera.is_active && has_perspective_projection {
entity_commands.insert(taa_settings.clone());
taa_settings.reset = false;
if taa_settings.is_some() && camera.is_active && has_perspective_projection {
entity_commands.insert(taa_settings.as_deref().unwrap().clone());
taa_settings.as_mut().unwrap().reset = false;
} else {
// TODO: needs better strategy for cleaning up
entity_commands.remove::<(
TemporalAntiAliasing,
// components added in prepare systems (because `TemporalAntiAliasNode` does not query extracted components)
TemporalAntiAliasHistoryTextures,
TemporalAntiAliasPipelineId,
)>();
@ -379,13 +369,22 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld
}
}
fn prepare_taa_jitter_and_mip_bias(
fn prepare_taa_jitter(
frame_count: Res<FrameCount>,
mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With<TemporalAntiAliasing>>,
mut commands: Commands,
mut query: Query<
&mut TemporalJitter,
(
With<TemporalAntiAliasing>,
With<Camera3d>,
With<TemporalJitter>,
With<DepthPrepass>,
With<MotionVectorPrepass>,
),
>,
) {
// Halton sequence (2, 3) - 0.5, skipping i = 0
// Halton sequence (2, 3) - 0.5
let halton_sequence = [
vec2(0.0, 0.0),
vec2(0.0, -0.16666666),
vec2(-0.25, 0.16666669),
vec2(0.25, -0.3888889),
@ -393,17 +392,12 @@ fn prepare_taa_jitter_and_mip_bias(
vec2(0.125, 0.2777778),
vec2(-0.125, -0.2777778),
vec2(0.375, 0.055555582),
vec2(-0.4375, 0.3888889),
];
let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()];
for (entity, mut jitter, mip_bias) in &mut query {
for mut jitter in &mut query {
jitter.offset = offset;
if mip_bias.is_none() {
commands.entity(entity).insert(MipBias(-1.0));
}
}
}

View File

@ -3,7 +3,7 @@ name = "bevy_app"
version = "0.16.0-dev"
edition = "2024"
description = "Provides core App functionality for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
@ -71,6 +71,12 @@ web = [
"dep:console_error_panic_hook",
]
hotpatching = [
"bevy_ecs/hotpatching",
"dep:dioxus-devtools",
"dep:crossbeam-channel",
]
[dependencies]
# bevy
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
@ -87,8 +93,10 @@ variadics_please = "1.1"
tracing = { version = "0.1", default-features = false, optional = true }
log = { version = "0.4", default-features = false }
cfg-if = "1.0.0"
dioxus-devtools = { version = "0.7.0-alpha.1", optional = true }
crossbeam-channel = { version = "0.5.0", optional = true }
[target.'cfg(any(unix, windows))'.dependencies]
[target.'cfg(any(all(unix, not(target_os = "horizon")), windows))'.dependencies]
ctrlc = { version = "3.4.4", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]

View File

@ -1483,8 +1483,8 @@ mod tests {
component::Component,
entity::Entity,
event::{Event, EventWriter, Events},
lifecycle::RemovedComponents,
query::With,
removal_detection::RemovedComponents,
resource::Resource,
schedule::{IntoScheduleConfigs, ScheduleLabel},
system::{Commands, Query},

View File

@ -0,0 +1,42 @@
//! Utilities for hotpatching code.
extern crate alloc;
use alloc::sync::Arc;
use bevy_ecs::{event::EventWriter, HotPatched};
#[cfg(not(target_family = "wasm"))]
use dioxus_devtools::connect_subsecond;
use dioxus_devtools::subsecond;
pub use dioxus_devtools::subsecond::{call, HotFunction};
use crate::{Last, Plugin};
/// Plugin connecting to Dioxus CLI to enable hot patching.
#[derive(Default)]
pub struct HotPatchPlugin;
impl Plugin for HotPatchPlugin {
fn build(&self, app: &mut crate::App) {
let (sender, receiver) = crossbeam_channel::bounded::<HotPatched>(1);
// Connects to the dioxus CLI that will handle rebuilds
// This will open a connection to the dioxus CLI to receive updated jump tables
// Sends a `HotPatched` message through the channel when the jump table is updated
#[cfg(not(target_family = "wasm"))]
connect_subsecond();
subsecond::register_handler(Arc::new(move || {
sender.send(HotPatched).unwrap();
}));
// Adds a system that will read the channel for new `HotPatched`, and forward them as event to the ECS
app.add_event::<HotPatched>().add_systems(
Last,
move |mut events: EventWriter<HotPatched>| {
if receiver.try_recv().is_ok() {
events.write_default();
}
},
);
}
}

View File

@ -8,8 +8,8 @@
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#![no_std]
@ -28,21 +28,26 @@ mod main_schedule;
mod panic_handler;
mod plugin;
mod plugin_group;
mod propagate;
mod schedule_runner;
mod sub_app;
mod task_pool_plugin;
#[cfg(all(any(unix, windows), feature = "std"))]
#[cfg(all(any(all(unix, not(target_os = "horizon")), windows), feature = "std"))]
mod terminal_ctrl_c_handler;
#[cfg(feature = "hotpatching")]
pub mod hotpatch;
pub use app::*;
pub use main_schedule::*;
pub use panic_handler::*;
pub use plugin::*;
pub use plugin_group::*;
pub use propagate::*;
pub use schedule_runner::*;
pub use sub_app::*;
pub use task_pool_plugin::*;
#[cfg(all(any(unix, windows), feature = "std"))]
#[cfg(all(any(all(unix, not(target_os = "horizon")), windows), feature = "std"))]
pub use terminal_ctrl_c_handler::*;
/// The app prelude.

View File

@ -1,4 +1,4 @@
//! This module provides panic handlers for [Bevy](https://bevyengine.org)
//! This module provides panic handlers for [Bevy](https://bevy.org)
//! apps, and automatically configures platform specifics (i.e. Wasm or Android).
//!
//! By default, the [`PanicHandlerPlugin`] from this crate is included in Bevy's `DefaultPlugins`.

View File

@ -0,0 +1,551 @@
use alloc::vec::Vec;
use core::marker::PhantomData;
use crate::{App, Plugin, Update};
use bevy_ecs::{
component::Component,
entity::Entity,
hierarchy::ChildOf,
lifecycle::RemovedComponents,
query::{Changed, Or, QueryFilter, With, Without},
relationship::{Relationship, RelationshipTarget},
schedule::{IntoScheduleConfigs, SystemSet},
system::{Commands, Local, Query},
};
/// Plugin to automatically propagate a component value to all direct and transient relationship
/// targets (e.g. [`bevy_ecs::hierarchy::Children`]) of entities with a [`Propagate`] component.
///
/// The plugin Will maintain the target component over hierarchy changes, adding or removing
/// `C` when a relationship `R` (e.g. [`ChildOf`]) is added to or removed from a
/// relationship tree with a [`Propagate<C>`] source, or if the [`Propagate<C>`] component
/// is added, changed or removed.
///
/// Optionally you can include a query filter `F` to restrict the entities that are updated.
/// Note that the filter is not rechecked dynamically: changes to the filter state will not be
/// picked up until the [`Propagate`] component is touched, or the hierarchy is changed.
/// All members of the tree between source and target must match the filter for propagation
/// to reach a given target.
/// Individual entities can be skipped or terminate the propagation with the [`PropagateOver`]
/// and [`PropagateStop`] components.
pub struct HierarchyPropagatePlugin<
C: Component + Clone + PartialEq,
F: QueryFilter = (),
R: Relationship = ChildOf,
>(PhantomData<fn() -> (C, F, R)>);
/// Causes the inner component to be added to this entity and all direct and transient relationship
/// targets. A target with a [`Propagate<C>`] component of its own will override propagation from
/// that point in the tree.
#[derive(Component, Clone, PartialEq)]
pub struct Propagate<C: Component + Clone + PartialEq>(pub C);
/// Stops the output component being added to this entity.
/// Relationship targets will still inherit the component from this entity or its parents.
#[derive(Component)]
pub struct PropagateOver<C>(PhantomData<fn() -> C>);
/// Stops the propagation at this entity. Children will not inherit the component.
#[derive(Component)]
pub struct PropagateStop<C>(PhantomData<fn() -> C>);
/// The set in which propagation systems are added. You can schedule your logic relative to this set.
#[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)]
pub struct PropagateSet<C: Component + Clone + PartialEq> {
_p: PhantomData<fn() -> C>,
}
/// Internal struct for managing propagation
#[derive(Component, Clone, PartialEq)]
pub struct Inherited<C: Component + Clone + PartialEq>(pub C);
impl<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship> Default
for HierarchyPropagatePlugin<C, F, R>
{
fn default() -> Self {
Self(Default::default())
}
}
impl<C> Default for PropagateOver<C> {
fn default() -> Self {
Self(Default::default())
}
}
impl<C> Default for PropagateStop<C> {
fn default() -> Self {
Self(Default::default())
}
}
impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PropagateSet")
.field("_p", &self._p)
.finish()
}
}
impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self._p.hash(state);
}
}
impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> {
fn default() -> Self {
Self {
_p: Default::default(),
}
}
}
impl<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship> Plugin
for HierarchyPropagatePlugin<C, F, R>
{
fn build(&self, app: &mut App) {
app.add_systems(
Update,
(
update_source::<C, F>,
update_stopped::<C, F>,
update_reparented::<C, F, R>,
propagate_inherited::<C, F, R>,
propagate_output::<C, F>,
)
.chain()
.in_set(PropagateSet::<C>::default()),
);
}
}
/// add/remove `Inherited::<C>` and `C` for entities with a direct `Propagate::<C>`
pub fn update_source<C: Component + Clone + PartialEq, F: QueryFilter>(
mut commands: Commands,
changed: Query<
(Entity, &Propagate<C>),
(
Or<(Changed<Propagate<C>>, Without<Inherited<C>>)>,
Without<PropagateStop<C>>,
),
>,
mut removed: RemovedComponents<Propagate<C>>,
) {
for (entity, source) in &changed {
commands
.entity(entity)
.try_insert(Inherited(source.0.clone()));
}
for removed in removed.read() {
if let Ok(mut commands) = commands.get_entity(removed) {
commands.remove::<(Inherited<C>, C)>();
}
}
}
/// remove `Inherited::<C>` and `C` for entities with a `PropagateStop::<C>`
pub fn update_stopped<C: Component + Clone + PartialEq, F: QueryFilter>(
mut commands: Commands,
q: Query<Entity, (With<Inherited<C>>, With<PropagateStop<C>>, F)>,
) {
for entity in q.iter() {
let mut cmds = commands.entity(entity);
cmds.remove::<(Inherited<C>, C)>();
}
}
/// add/remove `Inherited::<C>` and `C` for entities which have changed relationship
pub fn update_reparented<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
mut commands: Commands,
moved: Query<
(Entity, &R, Option<&Inherited<C>>),
(
Changed<R>,
Without<Propagate<C>>,
Without<PropagateStop<C>>,
F,
),
>,
relations: Query<&Inherited<C>>,
orphaned: Query<Entity, (With<Inherited<C>>, Without<Propagate<C>>, Without<R>, F)>,
) {
for (entity, relation, maybe_inherited) in &moved {
if let Ok(inherited) = relations.get(relation.get()) {
commands.entity(entity).try_insert(inherited.clone());
} else if maybe_inherited.is_some() {
commands.entity(entity).remove::<(Inherited<C>, C)>();
}
}
for orphan in &orphaned {
commands.entity(orphan).remove::<(Inherited<C>, C)>();
}
}
/// add/remove `Inherited::<C>` for targets of entities with modified `Inherited::<C>`
pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
mut commands: Commands,
changed: Query<
(&Inherited<C>, &R::RelationshipTarget),
(Changed<Inherited<C>>, Without<PropagateStop<C>>, F),
>,
recurse: Query<
(Option<&R::RelationshipTarget>, Option<&Inherited<C>>),
(Without<Propagate<C>>, Without<PropagateStop<C>>, F),
>,
mut removed: RemovedComponents<Inherited<C>>,
mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>,
) {
// gather changed
for (inherited, targets) in &changed {
to_process.extend(
targets
.iter()
.map(|target| (target, Some(inherited.clone()))),
);
}
// and removed
for entity in removed.read() {
if let Ok((Some(targets), _)) = recurse.get(entity) {
to_process.extend(targets.iter().map(|target| (target, None)));
}
}
// propagate
while let Some((entity, maybe_inherited)) = (*to_process).pop() {
let Ok((maybe_targets, maybe_current)) = recurse.get(entity) else {
continue;
};
if maybe_current == maybe_inherited.as_ref() {
continue;
}
if let Some(targets) = maybe_targets {
to_process.extend(
targets
.iter()
.map(|target| (target, maybe_inherited.clone())),
);
}
if let Some(inherited) = maybe_inherited {
commands.entity(entity).try_insert(inherited.clone());
} else {
commands.entity(entity).remove::<(Inherited<C>, C)>();
}
}
}
/// add `C` to entities with `Inherited::<C>`
pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>(
mut commands: Commands,
changed: Query<
(Entity, &Inherited<C>, Option<&C>),
(Changed<Inherited<C>>, Without<PropagateOver<C>>, F),
>,
) {
for (entity, inherited, maybe_current) in &changed {
if maybe_current.is_some_and(|c| &inherited.0 == c) {
continue;
}
commands.entity(entity).try_insert(inherited.0.clone());
}
}
#[cfg(test)]
mod tests {
use bevy_ecs::schedule::Schedule;
use crate::{App, Update};
use super::*;
#[derive(Component, Clone, PartialEq, Debug)]
struct TestValue(u32);
#[test]
fn test_simple_propagate() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let intermediate = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(intermediate))
.id();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_ok());
}
#[test]
fn test_reparented() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_ok());
}
#[test]
fn test_reparented_with_prior() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator_a = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator_a))
.id();
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee),
Ok(&TestValue(1))
);
app.world_mut()
.commands()
.entity(propagatee)
.insert(ChildOf(propagator_b));
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee),
Ok(&TestValue(2))
);
}
#[test]
fn test_remove_orphan() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_ok());
app.world_mut()
.commands()
.entity(propagatee)
.remove::<ChildOf>();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_err());
}
#[test]
fn test_remove_propagated() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_ok());
app.world_mut()
.commands()
.entity(propagator)
.remove::<Propagate<TestValue>>();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_err());
}
#[test]
fn test_propagate_over() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_over = app
.world_mut()
.spawn(TestValue(2))
.insert(ChildOf(propagator))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_over))
.id();
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee),
Ok(&TestValue(1))
);
}
#[test]
fn test_propagate_stop() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagate_stop = app
.world_mut()
.spawn(PropagateStop::<TestValue>::default())
.insert(ChildOf(propagator))
.id();
let no_propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagate_stop))
.id();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), no_propagatee)
.is_err());
}
#[test]
fn test_intermediate_override() {
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let intermediate = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(intermediate))
.id();
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee),
Ok(&TestValue(1))
);
app.world_mut()
.entity_mut(intermediate)
.insert(Propagate(TestValue(2)));
app.update();
assert_eq!(
app.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee),
Ok(&TestValue(2))
);
}
#[test]
fn test_filter() {
#[derive(Component)]
struct Marker;
let mut app = App::new();
app.add_schedule(Schedule::new(Update));
app.add_plugins(HierarchyPropagatePlugin::<TestValue, With<Marker>>::default());
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
let propagatee = app
.world_mut()
.spawn_empty()
.insert(ChildOf(propagator))
.id();
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_err());
// NOTE: changes to the filter condition are not rechecked
app.world_mut().entity_mut(propagator).insert(Marker);
app.world_mut().entity_mut(propagatee).insert(Marker);
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_err());
app.world_mut()
.entity_mut(propagator)
.insert(Propagate(TestValue(1)));
app.update();
assert!(app
.world_mut()
.query::<&TestValue>()
.get(app.world(), propagatee)
.is_ok());
}
}

View File

@ -3,7 +3,7 @@ name = "bevy_asset"
version = "0.16.0-dev"
edition = "2024"
description = "Provides asset functionality for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -3,7 +3,7 @@ name = "bevy_asset_macros"
version = "0.16.0-dev"
edition = "2024"
description = "Derive implementations for bevy_asset"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -437,6 +437,18 @@ impl<A: Asset> Assets<A> {
result
}
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
///
/// This is the same as [`Assets::get_mut`] except it doesn't emit [`AssetEvent::Modified`].
#[inline]
pub fn get_mut_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
let id: AssetId<A> = id.into();
match id {
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
}
}
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists.
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
pub fn remove(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
@ -450,6 +462,8 @@ impl<A: Asset> Assets<A> {
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists. This skips emitting [`AssetEvent::Removed`].
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
///
/// This is the same as [`Assets::remove`] except it doesn't emit [`AssetEvent::Removed`].
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
let id: AssetId<A> = id.into();
self.duplicate_handles.remove(&id);

View File

@ -141,8 +141,8 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#![no_std]

View File

@ -12,7 +12,7 @@ use alloc::{
vec::Vec,
};
use atomicow::CowArc;
use bevy_ecs::world::World;
use bevy_ecs::{error::BevyError, world::World};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId};
@ -34,7 +34,7 @@ pub trait AssetLoader: Send + Sync + 'static {
/// The settings type used by this [`AssetLoader`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
type Error: Into<BevyError>;
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
fn load(
&self,
@ -58,10 +58,7 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
>;
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str];
@ -89,10 +86,7 @@ where
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
mut load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
> {
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
Box::pin(async move {
let settings = meta
.loader_settings()
@ -394,15 +388,15 @@ impl<'a> LoadContext<'a> {
/// result with [`LoadContext::add_labeled_asset`].
///
/// See [`AssetPath`] for more on labeled assets.
pub fn labeled_asset_scope<A: Asset>(
pub fn labeled_asset_scope<A: Asset, E>(
&mut self,
label: String,
load: impl FnOnce(&mut LoadContext) -> A,
) -> Handle<A> {
load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
) -> Result<Handle<A>, E> {
let mut context = self.begin_labeled_asset();
let asset = load(&mut context);
let asset = load(&mut context)?;
let loaded_asset = context.finish(asset);
self.add_loaded_labeled_asset(label, loaded_asset)
Ok(self.add_loaded_labeled_asset(label, loaded_asset))
}
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
@ -416,7 +410,8 @@ impl<'a> LoadContext<'a> {
///
/// See [`AssetPath`] for more on labeled assets.
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
self.labeled_asset_scope(label, |_| asset)
self.labeled_asset_scope(label, |_| Ok::<_, ()>(asset))
.expect("the closure returns Ok")
}
/// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context.

View File

@ -1945,7 +1945,7 @@ pub enum AssetLoadError {
pub struct AssetLoaderError {
path: AssetPath<'static>,
loader_name: &'static str,
error: Arc<dyn core::error::Error + Send + Sync + 'static>,
error: Arc<BevyError>,
}
impl AssetLoaderError {

View File

@ -3,7 +3,7 @@ name = "bevy_audio"
version = "0.16.0-dev"
edition = "2024"
description = "Provides audio functionality for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
@ -19,12 +19,18 @@ bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
# other
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
rodio = { version = "0.20", default-features = false }
tracing = { version = "0.1", default-features = false, features = ["std"] }
[target.'cfg(target_os = "android")'.dependencies]
cpal = { version = "0.15", optional = true }
[target.'cfg(target_vendor = "apple")'.dependencies]
# NOTE: Explicitly depend on this patch version to fix:
# https://github.com/bevyengine/bevy/issues/18893
coreaudio-sys = { version = "0.2.17", default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies]
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
rodio = { version = "0.20", default-features = false, features = [

View File

@ -1,8 +1,8 @@
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
//! Audio support for the game engine Bevy

View File

@ -34,7 +34,7 @@ impl GlobalVolume {
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Clone, Debug, PartialEq)]
pub enum Volume {
/// Create a new [`Volume`] from the given volume in linear scale.
/// Create a new [`Volume`] from the given volume in the linear scale.
///
/// In a linear scale, the value `1.0` represents the "normal" volume,
/// meaning the audio is played at its original level. Values greater than
@ -144,7 +144,7 @@ impl Volume {
/// Returns the volume in decibels as a float.
///
/// If the volume is silent / off / muted, i.e. its underlying linear scale
/// If the volume is silent / off / muted, i.e., its underlying linear scale
/// is `0.0`, this method returns negative infinity.
pub fn to_decibels(&self) -> f32 {
match self {
@ -155,57 +155,95 @@ impl Volume {
/// The silent volume. Also known as "off" or "muted".
pub const SILENT: Self = Volume::Linear(0.0);
}
impl core::ops::Add<Self> for Volume {
type Output = Self;
fn add(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a + b),
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
decibels_to_linear(a) + decibels_to_linear(b),
)),
// {Linear, Decibels} favors the left hand side of the operation by
// first converting the right hand side to the same type as the left
// hand side and then performing the operation.
(Linear(..), Decibels(db)) => self + Linear(decibels_to_linear(db)),
(Decibels(..), Linear(l)) => self + Decibels(linear_to_decibels(l)),
}
/// Increases the volume by the specified percentage.
///
/// This method works in the linear domain, where a 100% increase
/// means doubling the volume (equivalent to +6.02dB).
///
/// # Arguments
/// * `percentage` - The percentage to increase (50.0 means 50% increase)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let volume = Volume::Linear(1.0);
/// let increased = volume.increase_by_percentage(100.0);
/// assert_eq!(increased.to_linear(), 2.0);
/// ```
pub fn increase_by_percentage(&self, percentage: f32) -> Self {
let factor = 1.0 + (percentage / 100.0);
Volume::Linear(self.to_linear() * factor)
}
}
impl core::ops::AddAssign<Self> for Volume {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
/// Decreases the volume by the specified percentage.
///
/// This method works in the linear domain, where a 50% decrease
/// means halving the volume (equivalent to -6.02dB).
///
/// # Arguments
/// * `percentage` - The percentage to decrease (50.0 means 50% decrease)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let volume = Volume::Linear(1.0);
/// let decreased = volume.decrease_by_percentage(50.0);
/// assert_eq!(decreased.to_linear(), 0.5);
/// ```
pub fn decrease_by_percentage(&self, percentage: f32) -> Self {
let factor = 1.0 - (percentage / 100.0).clamp(0.0, 1.0);
Volume::Linear(self.to_linear() * factor)
}
}
impl core::ops::Sub<Self> for Volume {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a - b),
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
decibels_to_linear(a) - decibels_to_linear(b),
)),
// {Linear, Decibels} favors the left hand side of the operation by
// first converting the right hand side to the same type as the left
// hand side and then performing the operation.
(Linear(..), Decibels(db)) => self - Linear(decibels_to_linear(db)),
(Decibels(..), Linear(l)) => self - Decibels(linear_to_decibels(l)),
}
/// Scales the volume to a specific linear factor relative to the current volume.
///
/// This is different from `adjust_by_linear` as it sets the volume to be
/// exactly the factor times the original volume, rather than applying
/// the factor to the current volume.
///
/// # Arguments
/// * `factor` - The scaling factor (2.0 = twice as loud, 0.5 = half as loud)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let volume = Volume::Linear(0.8);
/// let scaled = volume.scale_to_factor(1.25);
/// assert_eq!(scaled.to_linear(), 1.0);
/// ```
pub fn scale_to_factor(&self, factor: f32) -> Self {
Volume::Linear(self.to_linear() * factor)
}
}
impl core::ops::SubAssign<Self> for Volume {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
/// Creates a fade effect by interpolating between current volume and target volume.
///
/// This method performs linear interpolation in the linear domain, which
/// provides a more natural-sounding fade effect.
///
/// # Arguments
/// * `target` - The target volume to fade towards
/// * `factor` - The interpolation factor (0.0 = current volume, 1.0 = target volume)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let current = Volume::Linear(1.0);
/// let target = Volume::Linear(0.0);
/// let faded = current.fade_towards(target, 0.5);
/// assert_eq!(faded.to_linear(), 0.5);
/// ```
pub fn fade_towards(&self, target: Volume, factor: f32) -> Self {
let current_linear = self.to_linear();
let target_linear = target.to_linear();
let factor_clamped = factor.clamp(0.0, 1.0);
let interpolated = current_linear + (target_linear - current_linear) * factor_clamped;
Volume::Linear(interpolated)
}
}
@ -337,8 +375,9 @@ mod tests {
Linear(f32::NEG_INFINITY).to_decibels().is_infinite(),
"Negative infinite linear scale is equivalent to infinite decibels"
);
assert!(
Decibels(f32::NEG_INFINITY).to_linear().abs() == 0.0,
assert_eq!(
Decibels(f32::NEG_INFINITY).to_linear().abs(),
0.0,
"Negative infinity decibels is equivalent to zero linear scale"
);
@ -361,6 +400,74 @@ mod tests {
);
}
#[test]
fn test_increase_by_percentage() {
let volume = Linear(1.0);
// 100% increase should double the volume
let increased = volume.increase_by_percentage(100.0);
assert_eq!(increased.to_linear(), 2.0);
// 50% increase
let increased = volume.increase_by_percentage(50.0);
assert_eq!(increased.to_linear(), 1.5);
}
#[test]
fn test_decrease_by_percentage() {
let volume = Linear(1.0);
// 50% decrease should halve the volume
let decreased = volume.decrease_by_percentage(50.0);
assert_eq!(decreased.to_linear(), 0.5);
// 25% decrease
let decreased = volume.decrease_by_percentage(25.0);
assert_eq!(decreased.to_linear(), 0.75);
// 100% decrease should result in silence
let decreased = volume.decrease_by_percentage(100.0);
assert_eq!(decreased.to_linear(), 0.0);
}
#[test]
fn test_scale_to_factor() {
let volume = Linear(0.8);
let scaled = volume.scale_to_factor(1.25);
assert_eq!(scaled.to_linear(), 1.0);
}
#[test]
fn test_fade_towards() {
let current = Linear(1.0);
let target = Linear(0.0);
// 50% fade should result in 0.5 linear volume
let faded = current.fade_towards(target, 0.5);
assert_eq!(faded.to_linear(), 0.5);
// 0% fade should keep current volume
let faded = current.fade_towards(target, 0.0);
assert_eq!(faded.to_linear(), 1.0);
// 100% fade should reach target volume
let faded = current.fade_towards(target, 1.0);
assert_eq!(faded.to_linear(), 0.0);
}
#[test]
fn test_decibel_math_properties() {
let volume = Linear(1.0);
// Adding 20dB should multiply linear volume by 10
let adjusted = volume * Decibels(20.0);
assert_approx_eq(adjusted, Linear(10.0));
// Subtracting 20dB should divide linear volume by 10
let adjusted = volume / Decibels(20.0);
assert_approx_eq(adjusted, Linear(0.1));
}
fn assert_approx_eq(a: Volume, b: Volume) {
const EPSILON: f32 = 0.0001;
@ -380,52 +487,6 @@ mod tests {
}
}
#[test]
fn volume_ops_add() {
// Linear to Linear.
assert_approx_eq(Linear(0.5) + Linear(0.5), Linear(1.0));
assert_approx_eq(Linear(0.5) + Linear(0.1), Linear(0.6));
assert_approx_eq(Linear(0.5) + Linear(-0.5), Linear(0.0));
// Decibels to Decibels.
assert_approx_eq(Decibels(0.0) + Decibels(0.0), Decibels(6.0206003));
assert_approx_eq(Decibels(6.0) + Decibels(6.0), Decibels(12.020599));
assert_approx_eq(Decibels(-6.0) + Decibels(-6.0), Decibels(0.020599423));
// {Linear, Decibels} favors the left hand side of the operation.
assert_approx_eq(Linear(0.5) + Decibels(0.0), Linear(1.5));
assert_approx_eq(Decibels(0.0) + Linear(0.5), Decibels(3.521825));
}
#[test]
fn volume_ops_add_assign() {
// Linear to Linear.
let mut volume = Linear(0.5);
volume += Linear(0.5);
assert_approx_eq(volume, Linear(1.0));
}
#[test]
fn volume_ops_sub() {
// Linear to Linear.
assert_approx_eq(Linear(0.5) - Linear(0.5), Linear(0.0));
assert_approx_eq(Linear(0.5) - Linear(0.1), Linear(0.4));
assert_approx_eq(Linear(0.5) - Linear(-0.5), Linear(1.0));
// Decibels to Decibels.
assert_eq!(Decibels(0.0) - Decibels(0.0), Decibels(f32::NEG_INFINITY));
assert_approx_eq(Decibels(6.0) - Decibels(4.0), Decibels(-7.736506));
assert_eq!(Decibels(-6.0) - Decibels(-6.0), Decibels(f32::NEG_INFINITY));
}
#[test]
fn volume_ops_sub_assign() {
// Linear to Linear.
let mut volume = Linear(0.5);
volume -= Linear(0.5);
assert_approx_eq(volume, Linear(0.0));
}
#[test]
fn volume_ops_mul() {
// Linear to Linear.

View File

@ -3,7 +3,7 @@ name = "bevy_color"
version = "0.16.0-dev"
edition = "2024"
description = "Types for representing and manipulating color values"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy", "color"]

View File

@ -1,8 +1,8 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#![no_std]
@ -262,6 +262,7 @@ macro_rules! impl_componentwise_vector_space {
}
impl bevy_math::VectorSpace for $ty {
type Scalar = f32;
const ZERO: Self = Self {
$($element: 0.0,)+
};

View File

@ -7,7 +7,7 @@ authors = [
"Carter Anderson <mcanders1@gmail.com>",
]
description = "Provides a core render pipeline for Bevy Engine."
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -2,8 +2,8 @@
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
pub mod auto_exposure;

View File

@ -1,7 +1,7 @@
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
use bevy_app::prelude::*;
use bevy_ecs::{component::*, prelude::*};
use bevy_ecs::{component::*, lifecycle::ComponentHook, prelude::*};
use bevy_math::UVec2;
use bevy_platform::collections::HashSet;
use bevy_platform::time::Instant;

View File

@ -0,0 +1,32 @@
[package]
name = "bevy_core_widgets"
version = "0.16.0-dev"
edition = "2024"
description = "Unstyled common widgets for Bevy Engine"
homepage = "https://bevyengine.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_a11y = { path = "../bevy_a11y", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev" }
# other
accesskit = "0.19"
[features]
default = []
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
all-features = true

View File

@ -0,0 +1,141 @@
use accesskit::Role;
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin};
use bevy_ecs::query::Has;
use bevy_ecs::system::ResMut;
use bevy_ecs::{
component::Component,
entity::Entity,
observer::Trigger,
query::With,
system::{Commands, Query, SystemId},
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
use bevy_ui::{InteractionDisabled, Pressed};
/// Headless button widget. This widget maintains a "pressed" state, which is used to
/// indicate whether the button is currently being pressed by the user. It emits a `ButtonClicked`
/// event when the button is un-pressed.
#[derive(Component, Debug)]
#[require(AccessibilityNode(accesskit::Node::new(Role::Button)))]
pub struct CoreButton {
/// Optional system to run when the button is clicked, or when the Enter or Space key
/// is pressed while the button is focused. If this field is `None`, the button will
/// emit a `ButtonClicked` event when clicked.
pub on_click: Option<SystemId>,
}
fn button_on_key_event(
mut trigger: Trigger<FocusedInput<KeyboardInput>>,
q_state: Query<(&CoreButton, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((bstate, disabled)) = q_state.get(trigger.target().unwrap()) {
if !disabled {
let event = &trigger.event().input;
if !event.repeat
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
{
if let Some(on_click) = bstate.on_click {
trigger.propagate(false);
commands.run_system(on_click);
}
}
}
}
}
fn button_on_pointer_click(
mut trigger: Trigger<Pointer<Click>>,
mut q_state: Query<(&CoreButton, Has<Pressed>, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(trigger.target().unwrap()) {
trigger.propagate(false);
if pressed && !disabled {
if let Some(on_click) = bstate.on_click {
commands.run_system(on_click);
}
}
}
}
fn button_on_pointer_down(
mut trigger: Trigger<Pointer<Press>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
focus: Option<ResMut<InputFocus>>,
focus_visible: Option<ResMut<InputFocusVisible>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
trigger.propagate(false);
if !disabled {
if !pressed {
commands.entity(button).insert(Pressed);
}
// Clicking on a button makes it the focused input,
// and hides the focus ring if it was visible.
if let Some(mut focus) = focus {
focus.0 = trigger.target();
}
if let Some(mut focus_visible) = focus_visible {
focus_visible.0 = false;
}
}
}
}
fn button_on_pointer_up(
mut trigger: Trigger<Pointer<Release>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
trigger.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
fn button_on_pointer_drag_end(
mut trigger: Trigger<Pointer<DragEnd>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
trigger.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
fn button_on_pointer_cancel(
mut trigger: Trigger<Pointer<Cancel>>,
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
mut commands: Commands,
) {
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
trigger.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
/// Plugin that adds the observers for the [`CoreButton`] widget.
pub struct CoreButtonPlugin;
impl Plugin for CoreButtonPlugin {
fn build(&self, app: &mut App) {
app.add_observer(button_on_key_event)
.add_observer(button_on_pointer_down)
.add_observer(button_on_pointer_up)
.add_observer(button_on_pointer_click)
.add_observer(button_on_pointer_drag_end)
.add_observer(button_on_pointer_cancel);
}
}

View File

@ -0,0 +1,27 @@
//! This crate provides a set of core widgets for Bevy UI, such as buttons, checkboxes, and sliders.
//! These widgets have no inherent styling, it's the responsibility of the user to add styling
//! appropriate for their game or application.
//!
//! # State Management
//!
//! Most of the widgets use external state management: this means that the widgets do not
//! automatically update their own internal state, but instead rely on the app to update the widget
//! state (as well as any other related game state) in response to a change event emitted by the
//! widget. The primary motivation for this is to avoid two-way data binding in scenarios where the
//! user interface is showing a live view of dynamic data coming from deeper within the game engine.
mod core_button;
use bevy_app::{App, Plugin};
pub use core_button::{CoreButton, CoreButtonPlugin};
/// A plugin that registers the observers for all of the core widgets. If you don't want to
/// use all of the widgets, you can import the individual widget plugins instead.
pub struct CoreWidgetsPlugin;
impl Plugin for CoreWidgetsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(CoreButtonPlugin);
}
}

View File

@ -3,7 +3,7 @@ name = "bevy_derive"
version = "0.16.0-dev"
edition = "2024"
description = "Provides derive implementations for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -2,7 +2,7 @@
name = "bevy_derive_compile_fail"
edition = "2024"
description = "Compile fail tests for Bevy Engine's various macros"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
publish = false

View File

@ -1,9 +1,10 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
//! Assorted proc macro derive functions.
#![forbid(unsafe_code)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
extern crate proc_macro;
@ -188,11 +189,34 @@ pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
derefs::derive_deref_mut(input)
}
/// Generates the required main function boilerplate for Android.
#[proc_macro_attribute]
pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream {
bevy_main::bevy_main(attr, item)
}
/// Adds `enum_variant_index` and `enum_variant_name` functions to enums.
///
/// # Example
///
/// ```
/// use bevy_derive::{EnumVariantMeta};
///
/// #[derive(EnumVariantMeta)]
/// enum MyEnum {
/// A,
/// B,
/// }
///
/// let a = MyEnum::A;
/// let b = MyEnum::B;
///
/// assert_eq!(0, a.enum_variant_index());
/// assert_eq!("A", a.enum_variant_name());
///
/// assert_eq!(1, b.enum_variant_index());
/// assert_eq!("B", b.enum_variant_name());
/// ```
#[proc_macro_derive(EnumVariantMeta)]
pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
enum_variant_meta::derive_enum_variant_meta(input)

View File

@ -3,7 +3,7 @@ name = "bevy_dev_tools"
version = "0.16.0-dev"
edition = "2024"
description = "Collection of developer tools for the Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -1,11 +1,11 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
//! This crate provides additional utilities for the [Bevy game engine](https://bevyengine.org),
//! This crate provides additional utilities for the [Bevy game engine](https://bevy.org),
//! focused on improving developer experience.
use bevy_app::prelude::*;

View File

@ -94,8 +94,8 @@ impl Plugin for DebugPickingPlugin {
log_event_debug::<pointer::PointerInput>.run_if(DebugPickingMode::is_noisy),
log_pointer_event_debug::<Over>,
log_pointer_event_debug::<Out>,
log_pointer_event_debug::<Pressed>,
log_pointer_event_debug::<Released>,
log_pointer_event_debug::<Press>,
log_pointer_event_debug::<Release>,
log_pointer_event_debug::<Click>,
log_pointer_event_trace::<Move>.run_if(DebugPickingMode::is_noisy),
log_pointer_event_debug::<DragStart>,

View File

@ -3,7 +3,7 @@ name = "bevy_diagnostic"
version = "0.16.0-dev"
edition = "2024"
description = "Provides diagnostic functionality for Bevy Engine"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -19,8 +19,10 @@ impl Plugin for EntityCountDiagnosticsPlugin {
}
impl EntityCountDiagnosticsPlugin {
/// Number of currently allocated entities.
pub const ENTITY_COUNT: DiagnosticPath = DiagnosticPath::const_new("entity_count");
/// Updates entity count measurement.
pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) {
diagnostics.add_measurement(&Self::ENTITY_COUNT, || entities.count_constructed() as f64);
}

View File

@ -58,10 +58,16 @@ impl Plugin for FrameTimeDiagnosticsPlugin {
}
impl FrameTimeDiagnosticsPlugin {
/// Frames per second.
pub const FPS: DiagnosticPath = DiagnosticPath::const_new("fps");
/// Total frames since application start.
pub const FRAME_COUNT: DiagnosticPath = DiagnosticPath::const_new("frame_count");
/// Frame time in ms.
pub const FRAME_TIME: DiagnosticPath = DiagnosticPath::const_new("frame_time");
/// Updates frame count, frame time and fps measurements.
pub fn diagnostic_system(
mut diagnostics: Diagnostics,
time: Res<Time<Real>>,

View File

@ -1,13 +1,12 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![forbid(unsafe_code)]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#![no_std]
//! This crate provides a straightforward solution for integrating diagnostics in the [Bevy game engine](https://bevyengine.org/).
//! This crate provides a straightforward solution for integrating diagnostics in the [Bevy game engine](https://bevy.org/).
//! It allows users to easily add diagnostic functionality to their Bevy applications, enhancing
//! their ability to monitor and optimize their game's.

View File

@ -3,7 +3,7 @@ name = "bevy_dylib"
version = "0.16.0-dev"
edition = "2024"
description = "Force the Bevy Engine to be dynamically linked for faster linking"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]

View File

@ -1,7 +1,7 @@
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
//! Forces dynamic linking of Bevy.

View File

@ -3,7 +3,7 @@ name = "bevy_ecs"
version = "0.16.0-dev"
edition = "2024"
description = "Bevy Engine's entity component system"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["ecs", "game", "bevy"]
@ -83,6 +83,8 @@ critical-section = [
"bevy_reflect?/critical-section",
]
hotpatching = ["dep:subsecond"]
[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
@ -117,6 +119,7 @@ variadics_please = { version = "1.1", default-features = false }
tracing = { version = "0.1", default-features = false, optional = true }
log = { version = "0.4", default-features = false }
bumpalo = "3"
subsecond = { version = "0.7.0-alpha.1", optional = true }
concurrent-queue = { version = "2.5.0", default-features = false }
[target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies]

View File

@ -340,8 +340,8 @@ let mut world = World::new();
let entity = world.spawn_empty().id();
world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
println!("Entity {} goes BOOM!", trigger.target());
commands.entity(trigger.target()).despawn();
println!("Entity {} goes BOOM!", trigger.target().unwrap());
commands.entity(trigger.target().unwrap()).despawn();
});
world.flush();
@ -349,4 +349,4 @@ world.flush();
world.trigger_targets(Explode, entity);
```
[bevy]: https://bevyengine.org/
[bevy]: https://bevy.org/

View File

@ -2,7 +2,7 @@
name = "bevy_ecs_compile_fail"
edition = "2024"
description = "Compile fail tests for Bevy Engine's entity component system"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
publish = false

View File

@ -60,4 +60,4 @@ mod case4 {
pub struct BarTargetOf(Entity);
}
fn foo_hook(_world: bevy_ecs::world::DeferredWorld, _ctx: bevy_ecs::component::HookContext) {}
fn foo_hook(_world: bevy_ecs::world::DeferredWorld, _ctx: bevy_ecs::lifecycle::HookContext) {}

View File

@ -434,7 +434,7 @@ impl HookAttributeKind {
HookAttributeKind::Path(path) => path.to_token_stream(),
HookAttributeKind::Call(call) => {
quote!({
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::component::HookContext) {
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {
(#call)(world, ctx)
}
_internal_hook
@ -658,7 +658,7 @@ fn hook_register_function_call(
) -> Option<TokenStream2> {
function.map(|meta| {
quote! {
fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> {
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
::core::option::Option::Some(#meta)
}
}

View File

@ -1,4 +1,5 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
//! Macros for deriving ECS traits.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
extern crate proc_macro;
@ -28,12 +29,48 @@ enum BundleFieldKind {
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
const BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS: &str = "ignore_from_components";
#[derive(Debug)]
struct BundleAttributes {
impl_from_components: bool,
}
impl Default for BundleAttributes {
fn default() -> Self {
Self {
impl_from_components: true,
}
}
}
/// Implement the `Bundle` trait.
#[proc_macro_derive(Bundle, attributes(bundle))]
pub fn derive_bundle(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path();
let mut errors = vec![];
let mut attributes = BundleAttributes::default();
for attr in &ast.attrs {
if attr.path().is_ident(BUNDLE_ATTRIBUTE_NAME) {
let parsing = attr.parse_nested_meta(|meta| {
if meta.path.is_ident(BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS) {
attributes.impl_from_components = false;
return Ok(());
}
Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`")))
});
if let Err(error) = parsing {
errors.push(error.into_compile_error());
}
}
}
let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
Ok(fields) => fields,
Err(e) => return e.into_compile_error().into(),
@ -128,7 +165,28 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let struct_name = &ast.ident;
let from_components = attributes.impl_from_components.then(|| quote! {
// SAFETY:
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
#[allow(deprecated)]
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
{
Self{
#(#field_from_components)*
}
}
}
});
let attribute_errors = &errors;
TokenStream::from(quote! {
#(#attribute_errors)*
// SAFETY:
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
@ -157,20 +215,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}
}
// SAFETY:
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
#[allow(deprecated)]
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
{
Self{
#(#field_from_components)*
}
}
}
#from_components
#[allow(deprecated)]
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
@ -187,6 +232,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
})
}
/// Implement the `MapEntities` trait.
#[proc_macro_derive(MapEntities, attributes(entities))]
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
@ -522,16 +568,19 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
BevyManifest::shared().get_path("bevy_ecs")
}
/// Implement the `Event` trait.
#[proc_macro_derive(Event, attributes(event))]
pub fn derive_event(input: TokenStream) -> TokenStream {
component::derive_event(input)
}
/// Implement the `Resource` trait.
#[proc_macro_derive(Resource)]
pub fn derive_resource(input: TokenStream) -> TokenStream {
component::derive_resource(input)
}
/// Implement the `Component` trait.
#[proc_macro_derive(
Component,
attributes(component, require, relationship, relationship_target, entities)
@ -540,6 +589,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
}
/// Implement the `FromWorld` trait.
#[proc_macro_derive(FromWorld, attributes(from_world))]
pub fn derive_from_world(input: TokenStream) -> TokenStream {
let bevy_ecs_path = bevy_ecs_path();

View File

@ -23,17 +23,22 @@ use crate::{
bundle::BundleId,
component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
entity::{Entity, EntityLocation},
event::Event,
observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
};
use alloc::{boxed::Box, vec::Vec};
use bevy_platform::collections::HashMap;
use bevy_platform::collections::{hash_map::Entry, HashMap};
use core::{
hash::Hash,
ops::{Index, IndexMut, RangeFrom},
};
use nonmax::NonMaxU32;
#[derive(Event)]
#[expect(dead_code, reason = "Prepare for the upcoming Query as Entities")]
pub(crate) struct ArchetypeCreated(pub ArchetypeId);
/// An opaque location within a [`Archetype`].
///
/// This can be used in conjunction with [`ArchetypeId`] to find the exact location
@ -688,7 +693,7 @@ impl Archetype {
/// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer
///
/// [`OnAdd`]: crate::world::OnAdd
/// [`OnAdd`]: crate::lifecycle::OnAdd
#[inline]
pub fn has_add_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
@ -696,7 +701,7 @@ impl Archetype {
/// Returns true if any of the components in this archetype have at least one [`OnInsert`] observer
///
/// [`OnInsert`]: crate::world::OnInsert
/// [`OnInsert`]: crate::lifecycle::OnInsert
#[inline]
pub fn has_insert_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
@ -704,7 +709,7 @@ impl Archetype {
/// Returns true if any of the components in this archetype have at least one [`OnReplace`] observer
///
/// [`OnReplace`]: crate::world::OnReplace
/// [`OnReplace`]: crate::lifecycle::OnReplace
#[inline]
pub fn has_replace_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER)
@ -712,7 +717,7 @@ impl Archetype {
/// Returns true if any of the components in this archetype have at least one [`OnRemove`] observer
///
/// [`OnRemove`]: crate::world::OnRemove
/// [`OnRemove`]: crate::lifecycle::OnRemove
#[inline]
pub fn has_remove_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
@ -720,7 +725,7 @@ impl Archetype {
/// Returns true if any of the components in this archetype have at least one [`OnDespawn`] observer
///
/// [`OnDespawn`]: crate::world::OnDespawn
/// [`OnDespawn`]: crate::lifecycle::OnDespawn
#[inline]
pub fn has_despawn_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER)
@ -869,6 +874,10 @@ impl Archetypes {
}
/// Gets the archetype id matching the given inputs or inserts a new one if it doesn't exist.
///
/// Specifically, it returns a tuple where the first element
/// is the [`ArchetypeId`] that the given inputs belong to, and the second element is a boolean indicating whether a new archetype was created.
///
/// `table_components` and `sparse_set_components` must be sorted
///
/// # Safety
@ -881,7 +890,7 @@ impl Archetypes {
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId {
) -> (ArchetypeId, bool) {
let archetype_identity = ArchetypeComponents {
sparse_set_components: sparse_set_components.into_boxed_slice(),
table_components: table_components.into_boxed_slice(),
@ -889,14 +898,13 @@ impl Archetypes {
let archetypes = &mut self.archetypes;
let component_index = &mut self.by_component;
*self
.by_components
.entry(archetype_identity)
.or_insert_with_key(move |identity| {
match self.by_components.entry(archetype_identity) {
Entry::Occupied(occupied) => (*occupied.get(), false),
Entry::Vacant(vacant) => {
let ArchetypeComponents {
table_components,
sparse_set_components,
} = identity;
} = vacant.key();
let id = ArchetypeId::new(archetypes.len());
archetypes.push(Archetype::new(
components,
@ -907,8 +915,10 @@ impl Archetypes {
table_components.iter().copied(),
sparse_set_components.iter().copied(),
));
id
})
vacant.insert(id);
(id, true)
}
}
}
/// Clears all entities from all archetypes.

View File

@ -2,12 +2,63 @@
//!
//! This module contains the [`Bundle`] trait and some other helper types.
/// Derive the [`Bundle`] trait
///
/// You can apply this derive macro to structs that are
/// composed of [`Component`]s or
/// other [`Bundle`]s.
///
/// ## Attributes
///
/// Sometimes parts of the Bundle should not be inserted.
/// Those can be marked with `#[bundle(ignore)]`, and they will be skipped.
/// In that case, the field needs to implement [`Default`] unless you also ignore
/// the [`BundleFromComponents`] implementation.
///
/// ```rust
/// # use bevy_ecs::prelude::{Component, Bundle};
/// # #[derive(Component)]
/// # struct Hitpoint;
/// #
/// #[derive(Bundle)]
/// struct HitpointMarker {
/// hitpoints: Hitpoint,
///
/// #[bundle(ignore)]
/// creator: Option<String>
/// }
/// ```
///
/// Some fields may be bundles that do not implement
/// [`BundleFromComponents`]. This happens for bundles that cannot be extracted.
/// For example with [`SpawnRelatedBundle`](bevy_ecs::spawn::SpawnRelatedBundle), see below for an
/// example usage.
/// In those cases you can either ignore it as above,
/// or you can opt out the whole Struct by marking it as ignored with
/// `#[bundle(ignore_from_components)]`.
///
/// ```rust
/// # use bevy_ecs::prelude::{Component, Bundle, ChildOf, Spawn};
/// # #[derive(Component)]
/// # struct Hitpoint;
/// # #[derive(Component)]
/// # struct Marker;
/// #
/// use bevy_ecs::spawn::SpawnRelatedBundle;
///
/// #[derive(Bundle)]
/// #[bundle(ignore_from_components)]
/// struct HitpointMarker {
/// hitpoints: Hitpoint,
/// related_spawner: SpawnRelatedBundle<ChildOf, Spawn<Marker>>,
/// }
/// ```
pub use bevy_ecs_macros::Bundle;
use crate::{
archetype::{
Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus,
ComponentStatus, SpawnBundleStatus,
Archetype, ArchetypeAfterBundleInsert, ArchetypeCreated, ArchetypeId, Archetypes,
BundleComponentStatus, ComponentStatus, SpawnBundleStatus,
},
change_detection::MaybeLocation,
component::{
@ -15,15 +66,13 @@ use crate::{
RequiredComponents, StorageType, Tick,
},
entity::{Entities, EntitiesAllocator, Entity, EntityLocation},
lifecycle::{ON_ADD, ON_INSERT, ON_REMOVE, ON_REPLACE},
observer::Observers,
prelude::World,
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::{
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
ON_REPLACE,
},
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut},
};
use alloc::{boxed::Box, vec, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
@ -732,7 +781,7 @@ impl BundleInfo {
}
}
/// Inserts a bundle into the given archetype and returns the resulting archetype.
/// Inserts a bundle into the given archetype and returns the resulting archetype and whether a new archetype was created.
/// This could be the same [`ArchetypeId`], in the event that inserting the given bundle
/// does not result in an [`Archetype`] change.
///
@ -747,12 +796,12 @@ impl BundleInfo {
components: &Components,
observers: &Observers,
archetype_id: ArchetypeId,
) -> ArchetypeId {
) -> (ArchetypeId, bool) {
if let Some(archetype_after_insert_id) = archetypes[archetype_id]
.edges()
.get_archetype_after_bundle_insert(self.id)
{
return archetype_after_insert_id;
return (archetype_after_insert_id, false);
}
let mut new_table_components = Vec::new();
let mut new_sparse_set_components = Vec::new();
@ -806,7 +855,7 @@ impl BundleInfo {
added,
existing,
);
archetype_id
(archetype_id, false)
} else {
let table_id;
let table_components;
@ -842,13 +891,14 @@ impl BundleInfo {
};
};
// SAFETY: ids in self must be valid
let new_archetype_id = archetypes.get_id_or_insert(
let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert(
components,
observers,
table_id,
table_components,
sparse_set_components,
);
// Add an edge from the old archetype to the new archetype.
archetypes[archetype_id]
.edges_mut()
@ -860,11 +910,11 @@ impl BundleInfo {
added,
existing,
);
new_archetype_id
(new_archetype_id, is_new_created)
}
}
/// Removes a bundle from the given archetype and returns the resulting archetype
/// Removes a bundle from the given archetype and returns the resulting archetype and whether a new archetype was created.
/// (or `None` if the removal was invalid).
/// This could be the same [`ArchetypeId`], in the event that removing the given bundle
/// does not result in an [`Archetype`] change.
@ -887,7 +937,7 @@ impl BundleInfo {
observers: &Observers,
archetype_id: ArchetypeId,
intersection: bool,
) -> Option<ArchetypeId> {
) -> (Option<ArchetypeId>, bool) {
// Check the archetype graph to see if the bundle has been
// removed from this archetype in the past.
let archetype_after_remove_result = {
@ -898,9 +948,9 @@ impl BundleInfo {
edges.get_archetype_after_bundle_take(self.id())
}
};
let result = if let Some(result) = archetype_after_remove_result {
let (result, is_new_created) = if let Some(result) = archetype_after_remove_result {
// This bundle removal result is cached. Just return that!
result
(result, false)
} else {
let mut next_table_components;
let mut next_sparse_set_components;
@ -925,7 +975,7 @@ impl BundleInfo {
current_archetype
.edges_mut()
.cache_archetype_after_bundle_take(self.id(), None);
return None;
return (None, false);
}
}
@ -953,14 +1003,14 @@ impl BundleInfo {
};
}
let new_archetype_id = archetypes.get_id_or_insert(
let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert(
components,
observers,
next_table_id,
next_table_components,
next_sparse_set_components,
);
Some(new_archetype_id)
(Some(new_archetype_id), is_new_created)
};
let current_archetype = &mut archetypes[archetype_id];
// Cache the result in an edge.
@ -973,7 +1023,7 @@ impl BundleInfo {
.edges_mut()
.cache_archetype_after_bundle_take(self.id(), result);
}
result
(result, is_new_created)
}
}
@ -1036,14 +1086,15 @@ impl<'w> BundleInserter<'w> {
// SAFETY: We will not make any accesses to the command queue, component or resource data of this world
let bundle_info = world.bundles.get_unchecked(bundle_id);
let bundle_id = bundle_info.id();
let new_archetype_id = bundle_info.insert_bundle_into_archetype(
let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
archetype_id,
);
if new_archetype_id == archetype_id {
let inserter = if new_archetype_id == archetype_id {
let archetype = &mut world.archetypes[archetype_id];
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
let archetype_after_insert = unsafe {
@ -1103,7 +1154,15 @@ impl<'w> BundleInserter<'w> {
world: world.as_unsafe_world_cell(),
}
}
};
if is_new_created {
inserter
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
inserter
}
/// # Safety
@ -1133,7 +1192,7 @@ impl<'w> BundleInserter<'w> {
if archetype.has_replace_observer() {
deferred_world.trigger_observers(
ON_REPLACE,
entity,
Some(entity),
archetype_after_insert.iter_existing(),
caller,
);
@ -1318,7 +1377,7 @@ impl<'w> BundleInserter<'w> {
if new_archetype.has_add_observer() {
deferred_world.trigger_observers(
ON_ADD,
entity,
Some(entity),
archetype_after_insert.iter_added(),
caller,
);
@ -1336,7 +1395,7 @@ impl<'w> BundleInserter<'w> {
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(
ON_INSERT,
entity,
Some(entity),
archetype_after_insert.iter_inserted(),
caller,
);
@ -1355,7 +1414,7 @@ impl<'w> BundleInserter<'w> {
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(
ON_INSERT,
entity,
Some(entity),
archetype_after_insert.iter_added(),
caller,
);
@ -1421,7 +1480,7 @@ impl<'w> BundleRemover<'w> {
) -> Option<Self> {
let bundle_info = world.bundles.get_unchecked(bundle_id);
// SAFETY: Caller ensures archetype and bundle ids are correct.
let new_archetype_id = unsafe {
let (new_archetype_id, is_new_created) = unsafe {
bundle_info.remove_bundle_from_archetype(
&mut world.archetypes,
&mut world.storages,
@ -1429,11 +1488,14 @@ impl<'w> BundleRemover<'w> {
&world.observers,
archetype_id,
!require_all,
)?
)
};
let new_archetype_id = new_archetype_id?;
if new_archetype_id == archetype_id {
return None;
}
let (old_archetype, new_archetype) =
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
@ -1447,13 +1509,20 @@ impl<'w> BundleRemover<'w> {
Some((old.into(), new.into()))
};
Some(Self {
let remover = Self {
bundle_info: bundle_info.into(),
new_archetype: new_archetype.into(),
old_archetype: old_archetype.into(),
old_and_new_table: tables,
world: world.as_unsafe_world_cell(),
})
};
if is_new_created {
remover
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
Some(remover)
}
/// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing.
@ -1499,7 +1568,7 @@ impl<'w> BundleRemover<'w> {
if self.old_archetype.as_ref().has_replace_observer() {
deferred_world.trigger_observers(
ON_REPLACE,
entity,
Some(entity),
bundle_components_in_archetype(),
caller,
);
@ -1514,7 +1583,7 @@ impl<'w> BundleRemover<'w> {
if self.old_archetype.as_ref().has_remove_observer() {
deferred_world.trigger_observers(
ON_REMOVE,
entity,
Some(entity),
bundle_components_in_archetype(),
caller,
);
@ -1675,22 +1744,30 @@ impl<'w> BundleSpawner<'w> {
change_tick: Tick,
) -> Self {
let bundle_info = world.bundles.get_unchecked(bundle_id);
let new_archetype_id = bundle_info.insert_bundle_into_archetype(
let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
ArchetypeId::EMPTY,
);
let archetype = &mut world.archetypes[new_archetype_id];
let table = &mut world.storages.tables[archetype.table_id()];
Self {
let spawner = Self {
bundle_info: bundle_info.into(),
table: table.into(),
archetype: archetype.into(),
change_tick,
world: world.as_unsafe_world_cell(),
};
if is_new_created {
spawner
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
spawner
}
#[inline]
@ -1770,7 +1847,7 @@ impl<'w> BundleSpawner<'w> {
if archetype.has_add_observer() {
deferred_world.trigger_observers(
ON_ADD,
entity,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
@ -1785,7 +1862,7 @@ impl<'w> BundleSpawner<'w> {
if archetype.has_insert_observer() {
deferred_world.trigger_observers(
ON_INSERT,
entity,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
@ -2056,7 +2133,9 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
#[cfg(test)]
mod tests {
use crate::{component::HookContext, prelude::*, world::DeferredWorld};
use crate::{
archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld,
};
use alloc::vec;
#[derive(Component)]
@ -2105,6 +2184,26 @@ mod tests {
}
}
#[derive(Bundle)]
#[bundle(ignore_from_components)]
struct BundleNoExtract {
b: B,
no_from_comp: crate::spawn::SpawnRelatedBundle<ChildOf, Spawn<C>>,
}
#[test]
fn can_spawn_bundle_without_extract() {
let mut world = World::new();
let id = world
.spawn(BundleNoExtract {
b: B,
no_from_comp: Children::spawn(Spawn(C)),
})
.id();
assert!(world.entity(id).get::<Children>().is_some());
}
#[test]
fn component_hook_order_spawn_despawn() {
let mut world = World::new();
@ -2293,4 +2392,23 @@ mod tests {
assert_eq!(a, vec![1]);
}
#[test]
fn new_archetype_created() {
let mut world = World::new();
#[derive(Resource, Default)]
struct Count(u32);
world.init_resource::<Count>();
world.add_observer(|_t: Trigger<ArchetypeCreated>, mut count: ResMut<Count>| {
count.0 += 1;
});
let mut e = world.spawn((A, B));
e.insert(C);
e.remove::<A>();
e.insert(A);
e.insert(A);
assert_eq!(world.resource::<Count>().0, 3);
}
}

View File

@ -898,63 +898,39 @@ impl_debug!(Ref<'w, T>,);
/// Unique mutable borrow of an entity's component or of a resource.
///
/// This can be used in queries to opt into change detection on both their mutable and immutable forms, as opposed to
/// `&mut T`, which only provides access to change detection while in its mutable form:
/// This can be used in queries to access change detection from immutable query methods, as opposed
/// to `&mut T` which only provides access to change detection from mutable query methods.
///
/// ```rust
/// # use bevy_ecs::prelude::*;
/// # use bevy_ecs::query::QueryData;
/// #
/// #[derive(Component, Clone)]
/// #[derive(Component, Clone, Debug)]
/// struct Name(String);
///
/// #[derive(Component, Clone, Copy)]
/// #[derive(Component, Clone, Copy, Debug)]
/// struct Health(f32);
///
/// #[derive(Component, Clone, Copy)]
/// struct Position {
/// x: f32,
/// y: f32,
/// };
/// fn my_system(mut query: Query<(Mut<Name>, &mut Health)>) {
/// // Mutable access provides change detection information for both parameters:
/// // - `name` has type `Mut<Name>`
/// // - `health` has type `Mut<Health>`
/// for (name, health) in query.iter_mut() {
/// println!("Name: {:?} (last changed {:?})", name, name.last_changed());
/// println!("Health: {:?} (last changed: {:?})", health, health.last_changed());
/// # println!("{}{}", name.0, health.0); // Silence dead_code warning
/// }
///
/// #[derive(Component, Clone, Copy)]
/// struct Player {
/// id: usize,
/// };
///
/// #[derive(QueryData)]
/// #[query_data(mutable)]
/// struct PlayerQuery {
/// id: &'static Player,
///
/// // Reacting to `PlayerName` changes is expensive, so we need to enable change detection when reading it.
/// name: Mut<'static, Name>,
///
/// health: &'static mut Health,
/// position: &'static mut Position,
/// }
///
/// fn update_player_avatars(players_query: Query<PlayerQuery>) {
/// // The item returned by the iterator is of type `PlayerQueryReadOnlyItem`.
/// for player in players_query.iter() {
/// if player.name.is_changed() {
/// // Update the player's name. This clones a String, and so is more expensive.
/// update_player_name(player.id, player.name.clone());
/// }
///
/// // Update the health bar.
/// update_player_health(player.id, *player.health);
///
/// // Update the player's position.
/// update_player_position(player.id, *player.position);
/// // Immutable access only provides change detection for `Name`:
/// // - `name` has type `Ref<Name>`
/// // - `health` has type `&Health`
/// for (name, health) in query.iter() {
/// println!("Name: {:?} (last changed {:?})", name, name.last_changed());
/// println!("Health: {:?}", health);
/// }
/// }
///
/// # bevy_ecs::system::assert_is_system(update_player_avatars);
///
/// # fn update_player_name(player: &Player, new_name: Name) {}
/// # fn update_player_health(player: &Player, new_health: Health) {}
/// # fn update_player_position(player: &Player, new_position: Position) {}
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
pub struct Mut<'w, T: ?Sized> {
pub(crate) value: &'w mut T,

View File

@ -5,16 +5,17 @@ use crate::{
bundle::BundleInfo,
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
lifecycle::{ComponentHook, ComponentHooks},
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
resource::Resource,
storage::{SparseSetIndex, SparseSets, Table, TableRow},
system::{Local, SystemParam},
world::{DeferredWorld, FromWorld, World},
world::{FromWorld, World},
};
use alloc::boxed::Box;
use alloc::{borrow::Cow, format, vec::Vec};
pub use bevy_ecs_macros::Component;
use bevy_ecs_macros::Event;
use bevy_platform::sync::Arc;
use bevy_platform::{
collections::{HashMap, HashSet},
@ -375,7 +376,8 @@ use thiserror::Error;
/// - `#[component(on_remove = on_remove_function)]`
///
/// ```
/// # use bevy_ecs::component::{Component, HookContext};
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::lifecycle::HookContext;
/// # use bevy_ecs::world::DeferredWorld;
/// # use bevy_ecs::entity::Entity;
/// # use bevy_ecs::component::ComponentId;
@ -404,7 +406,8 @@ use thiserror::Error;
/// This also supports function calls that yield closures
///
/// ```
/// # use bevy_ecs::component::{Component, HookContext};
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::lifecycle::HookContext;
/// # use bevy_ecs::world::DeferredWorld;
/// #
/// #[derive(Component)]
@ -656,244 +659,6 @@ pub enum StorageType {
SparseSet,
}
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext);
/// Context provided to a [`ComponentHook`].
#[derive(Clone, Copy, Debug)]
pub struct HookContext {
/// The [`Entity`] this hook was invoked for.
pub entity: Entity,
/// The [`ComponentId`] this hook was invoked for.
pub component_id: ComponentId,
/// The caller location is `Some` if the `track_caller` feature is enabled.
pub caller: MaybeLocation,
/// Configures how relationship hooks will run
pub relationship_hook_mode: RelationshipHookMode,
}
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
///
/// Hooks are functions that run when a component is added, overwritten, or removed from an entity.
/// These are intended to be used for structural side effects that need to happen when a component is added or removed,
/// and are not intended for general-purpose logic.
///
/// For example, you might use a hook to update a cached index when a component is added,
/// to clean up resources when a component is removed,
/// or to keep hierarchical data structures across entities in sync.
///
/// This information is stored in the [`ComponentInfo`] of the associated component.
///
/// There is two ways of configuring hooks for a component:
/// 1. Defining the relevant hooks on the [`Component`] implementation
/// 2. Using the [`World::register_component_hooks`] method
///
/// # Example 2
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_platform::collections::HashSet;
///
/// #[derive(Component)]
/// struct MyTrackedComponent;
///
/// #[derive(Resource, Default)]
/// struct TrackedEntities(HashSet<Entity>);
///
/// let mut world = World::new();
/// world.init_resource::<TrackedEntities>();
///
/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
/// assert!(tracked_component_query.iter(&world).next().is_none());
///
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, context| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.insert(context.entity);
/// });
///
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.remove(&context.entity);
/// });
///
/// let entity = world.spawn(MyTrackedComponent).id();
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(tracked_entities.0.contains(&entity));
///
/// world.despawn(entity);
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(!tracked_entities.0.contains(&entity));
/// ```
#[derive(Debug, Clone, Default)]
pub struct ComponentHooks {
pub(crate) on_add: Option<ComponentHook>,
pub(crate) on_insert: Option<ComponentHook>,
pub(crate) on_replace: Option<ComponentHook>,
pub(crate) on_remove: Option<ComponentHook>,
pub(crate) on_despawn: Option<ComponentHook>,
}
impl ComponentHooks {
pub(crate) fn update_from_component<C: Component + ?Sized>(&mut self) -> &mut Self {
if let Some(hook) = C::on_add() {
self.on_add(hook);
}
if let Some(hook) = C::on_insert() {
self.on_insert(hook);
}
if let Some(hook) = C::on_replace() {
self.on_replace(hook);
}
if let Some(hook) = C::on_remove() {
self.on_remove(hook);
}
if let Some(hook) = C::on_despawn() {
self.on_despawn(hook);
}
self
}
/// Register a [`ComponentHook`] that will be run when this component is added to an entity.
/// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as
/// adding all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
.expect("Component already has an on_add hook")
}
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
/// or replaced.
///
/// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
.expect("Component already has an on_insert hook")
}
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
/// such as being replaced (with `.insert`) or removed.
///
/// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced,
/// allowing access to the previous data just before it is dropped.
/// This hook does *not* run if the entity did not already have this component.
///
/// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_replace` hook
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_replace(hook)
.expect("Component already has an on_replace hook")
}
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
/// Despawning an entity counts as removing all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component already has an on_remove hook")
}
/// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
///
/// # Panics
///
/// Will panic if the component already has an `on_despawn` hook
pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_despawn(hook)
.expect("Component already has an on_despawn hook")
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
///
/// This is a fallible version of [`Self::on_add`].
///
/// Returns `None` if the component already has an `on_add` hook.
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_add.is_some() {
return None;
}
self.on_add = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
///
/// This is a fallible version of [`Self::on_insert`].
///
/// Returns `None` if the component already has an `on_insert` hook.
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_insert.is_some() {
return None;
}
self.on_insert = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed
///
/// This is a fallible version of [`Self::on_replace`].
///
/// Returns `None` if the component already has an `on_replace` hook.
pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_replace.is_some() {
return None;
}
self.on_replace = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity.
///
/// This is a fallible version of [`Self::on_remove`].
///
/// Returns `None` if the component already has an `on_remove` hook.
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_remove.is_some() {
return None;
}
self.on_remove = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
///
/// This is a fallible version of [`Self::on_despawn`].
///
/// Returns `None` if the component already has an `on_despawn` hook.
pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_despawn.is_some() {
return None;
}
self.on_despawn = Some(hook);
Some(self)
}
}
/// Stores metadata for a type of component or resource stored in a specific [`World`].
#[derive(Debug, Clone)]
pub struct ComponentInfo {
@ -2052,7 +1817,7 @@ impl Components {
}
/// Gets the metadata associated with the given component, if it is registered.
/// This will return `None` if the id is not regiserted or is queued.
/// This will return `None` if the id is not registered or is queued.
///
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
#[inline]
@ -2400,7 +2165,7 @@ impl Components {
/// * [`World::component_id()`]
#[inline]
pub fn valid_component_id<T: Component>(&self) -> Option<ComponentId> {
self.get_id(TypeId::of::<T>())
self.get_valid_id(TypeId::of::<T>())
}
/// Type-erased equivalent of [`Components::valid_resource_id()`].
@ -2431,7 +2196,7 @@ impl Components {
/// * [`Components::get_resource_id()`]
#[inline]
pub fn valid_resource_id<T: Resource>(&self) -> Option<ComponentId> {
self.get_resource_id(TypeId::of::<T>())
self.get_valid_resource_id(TypeId::of::<T>())
}
/// Type-erased equivalent of [`Components::component_id()`].
@ -2616,7 +2381,7 @@ impl Tick {
///
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
#[inline]
pub(crate) fn check_tick(&mut self, tick: Tick) -> bool {
pub 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.
@ -2629,6 +2394,41 @@ impl Tick {
}
}
/// An observer [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
///
/// # Example
///
/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
/// ticks automatically updated via [`World::check_change_ticks`], possibly causing `Tick`-related bugs on
/// long-running apps.
///
/// To fix that, add an observer for this event that calls the schedule's
/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_ecs::component::CheckChangeTicks;
///
/// #[derive(Resource)]
/// struct CustomSchedule(Schedule);
///
/// # let mut world = World::new();
/// world.add_observer(|tick: Trigger<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
/// schedule.0.check_change_ticks(tick.get());
/// });
/// ```
#[derive(Debug, Clone, Copy, Event)]
pub struct CheckChangeTicks(pub(crate) Tick);
impl CheckChangeTicks {
/// Get the `Tick` that can be used as the parameter of [`Tick::check_tick`].
pub fn get(self) -> Tick {
self.0
}
}
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
#[derive(Copy, Clone, Debug)]
pub struct TickCells<'a> {

View File

@ -711,7 +711,7 @@ impl<'w> EntityClonerBuilder<'w> {
/// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods.
pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
for type_id in ids {
if let Some(id) = self.world.components().get_id(type_id) {
if let Some(id) = self.world.components().get_valid_id(type_id) {
self.filter_allow(id);
}
}
@ -746,7 +746,7 @@ impl<'w> EntityClonerBuilder<'w> {
/// Extends the list of components that shouldn't be cloned by type ids.
pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
for type_id in ids {
if let Some(id) = self.world.components().get_id(type_id) {
if let Some(id) = self.world.components().get_valid_id(type_id) {
self.filter_deny(id);
}
}
@ -768,7 +768,7 @@ impl<'w> EntityClonerBuilder<'w> {
&mut self,
clone_behavior: ComponentCloneBehavior,
) -> &mut Self {
if let Some(id) = self.world.components().component_id::<T>() {
if let Some(id) = self.world.components().valid_component_id::<T>() {
self.entity_cloner
.clone_behavior_overrides
.insert(id, clone_behavior);
@ -793,7 +793,7 @@ impl<'w> EntityClonerBuilder<'w> {
/// Removes a previously set override of [`ComponentCloneBehavior`] for a component in this builder.
pub fn remove_clone_behavior_override<T: Component>(&mut self) -> &mut Self {
if let Some(id) = self.world.components().component_id::<T>() {
if let Some(id) = self.world.components().valid_component_id::<T>() {
self.entity_cloner.clone_behavior_overrides.remove(&id);
}
self

View File

@ -357,7 +357,10 @@ mod tests {
// Next allocated entity should be a further generation on the same index
let entity = world.spawn_empty().id();
assert_eq!(entity.index(), dead_ref.index());
assert!(entity.generation() > dead_ref.generation());
assert!(entity
.generation()
.cmp_approx(&dead_ref.generation())
.is_gt());
}
#[test]
@ -372,6 +375,9 @@ mod tests {
// Next allocated entity should be a further generation on the same index
let entity = world.spawn_empty().id();
assert_eq!(entity.index(), dead_ref.index());
assert!(entity.generation() > dead_ref.generation());
assert!(entity
.generation()
.cmp_approx(&dead_ref.generation())
.is_gt());
}
}

View File

@ -166,29 +166,6 @@ impl SparseSetIndex for EntityRow {
///
/// This should be treated as a opaque identifier, and its internal representation may be subject to change.
///
/// # Ordering
///
/// [`EntityGeneration`] implements [`Ord`].
/// Generations that are later will be [`Greater`](core::cmp::Ordering::Greater) than earlier ones.
///
/// ```
/// # use bevy_ecs::entity::EntityGeneration;
/// assert!(EntityGeneration::FIRST < EntityGeneration::FIRST.after_versions(400));
/// let (aliased, did_alias) = EntityGeneration::FIRST.after_versions(400).after_versions_and_could_alias(u32::MAX);
/// assert!(did_alias);
/// assert!(EntityGeneration::FIRST < aliased);
/// ```
///
/// Ordering will be incorrect for distant generations:
///
/// ```
/// # use bevy_ecs::entity::EntityGeneration;
/// // This ordering is wrong!
/// assert!(EntityGeneration::FIRST > EntityGeneration::FIRST.after_versions(400 + (1u32 << 31)));
/// ```
///
/// This strange behavior needed to account for aliasing.
///
/// # Aliasing
///
/// Internally [`EntityGeneration`] wraps a `u32`, so it can't represent *every* possible generation.
@ -216,6 +193,9 @@ impl EntityGeneration {
/// Represents the first generation of an [`EntityRow`].
pub const FIRST: Self = Self(0);
/// Non-wrapping difference between two generations after which a signed interpretation becomes negative.
const DIFF_MAX: u32 = 1u32 << 31;
/// Gets some bits that represent this value.
/// The bits are opaque and should not be regarded as meaningful.
#[inline(always)]
@ -247,18 +227,48 @@ impl EntityGeneration {
let raw = self.0.overflowing_add(versions);
(Self(raw.0), raw.1)
}
}
impl PartialOrd for EntityGeneration {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for EntityGeneration {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
let diff = self.0.wrapping_sub(other.0);
(1u32 << 31).cmp(&diff)
/// Compares two generations.
///
/// Generations that are later will be [`Greater`](core::cmp::Ordering::Greater) than earlier ones.
///
/// ```
/// # use bevy_ecs::entity::EntityGeneration;
/// # use core::cmp::Ordering;
/// let later_generation = EntityGeneration::FIRST.after_versions(400);
/// assert_eq!(EntityGeneration::FIRST.cmp_approx(&later_generation), Ordering::Less);
///
/// let (aliased, did_alias) = EntityGeneration::FIRST.after_versions(400).after_versions_and_could_alias(u32::MAX);
/// assert!(did_alias);
/// assert_eq!(EntityGeneration::FIRST.cmp_approx(&aliased), Ordering::Less);
/// ```
///
/// Ordering will be incorrect and [non-transitive](https://en.wikipedia.org/wiki/Transitive_relation)
/// for distant generations:
///
/// ```should_panic
/// # use bevy_ecs::entity::EntityGeneration;
/// # use core::cmp::Ordering;
/// let later_generation = EntityGeneration::FIRST.after_versions(3u32 << 31);
/// let much_later_generation = later_generation.after_versions(3u32 << 31);
///
/// // while these orderings are correct and pass assertions...
/// assert_eq!(EntityGeneration::FIRST.cmp_approx(&later_generation), Ordering::Less);
/// assert_eq!(later_generation.cmp_approx(&much_later_generation), Ordering::Less);
///
/// // ... this ordering is not and the assertion fails!
/// assert_eq!(EntityGeneration::FIRST.cmp_approx(&much_later_generation), Ordering::Less);
/// ```
///
/// Because of this, `EntityGeneration` does not implement `Ord`/`PartialOrd`.
#[inline]
pub const fn cmp_approx(&self, other: &Self) -> core::cmp::Ordering {
use core::cmp::Ordering;
match self.0.wrapping_sub(other.0) {
0 => Ordering::Equal,
1..Self::DIFF_MAX => Ordering::Greater,
_ => Ordering::Less,
}
}
}
@ -1433,6 +1443,24 @@ mod tests {
}
}
#[test]
fn entity_generation_is_approximately_ordered() {
use core::cmp::Ordering;
let old = EntityGeneration::FIRST;
let middle = old.after_versions(1);
let younger_before_ord_wrap = middle.after_versions(EntityGeneration::DIFF_MAX);
let younger_after_ord_wrap = younger_before_ord_wrap.after_versions(1);
assert_eq!(middle.cmp_approx(&old), Ordering::Greater);
assert_eq!(middle.cmp_approx(&middle), Ordering::Equal);
assert_eq!(middle.cmp_approx(&younger_before_ord_wrap), Ordering::Less);
assert_eq!(
middle.cmp_approx(&younger_after_ord_wrap),
Ordering::Greater
);
}
#[test]
fn entity_debug() {
let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap()));

View File

@ -68,7 +68,7 @@ pub trait Event: Send + Sync + 'static {
///
/// # Warning
///
/// This method should not be overridden by implementors,
/// This method should not be overridden by implementers,
/// and should always correspond to the implementation of [`component_id`](Event::component_id).
fn register_component_id(world: &mut World) -> ComponentId {
world.register_component::<EventWrapperComponent<Self>>()
@ -82,7 +82,7 @@ pub trait Event: Send + Sync + 'static {
///
/// # Warning
///
/// This method should not be overridden by implementors,
/// This method should not be overridden by implementers,
/// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id).
fn component_id(world: &World) -> Option<ComponentId> {
world.component_id::<EventWrapperComponent<Self>>()

View File

@ -10,8 +10,9 @@
use crate::reflect::{ReflectComponent, ReflectFromWorld};
use crate::{
bundle::Bundle,
component::{Component, HookContext},
component::Component,
entity::Entity,
lifecycle::HookContext,
relationship::{RelatedSpawner, RelatedSpawnerCommands},
system::EntityCommands,
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
@ -440,7 +441,7 @@ pub fn validate_parent_has_component<C: Component>(
let name: Option<String> = None;
warn!(
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004",
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = ShortName::of::<C>(),
name = name.map_or_else(

View File

@ -13,8 +13,8 @@
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")]
#![doc(
html_logo_url = "https://bevyengine.org/assets/icon.png",
html_favicon_url = "https://bevyengine.org/assets/icon.png"
html_logo_url = "https://bevy.org/assets/icon.png",
html_favicon_url = "https://bevy.org/assets/icon.png"
)]
#![no_std]
@ -41,6 +41,7 @@ pub mod event;
pub mod hierarchy;
pub mod intern;
pub mod label;
pub mod lifecycle;
pub mod name;
pub mod never;
pub mod observer;
@ -48,7 +49,6 @@ pub mod query;
#[cfg(feature = "bevy_reflect")]
pub mod reflect;
pub mod relationship;
pub mod removal_detection;
pub mod resource;
pub mod schedule;
pub mod spawn;
@ -59,6 +59,9 @@ pub mod world;
pub use bevy_ptr as ptr;
#[cfg(feature = "hotpatching")]
use event::Event;
/// The ECS prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
@ -73,12 +76,12 @@ pub mod prelude {
error::{BevyError, Result},
event::{Event, EventMutator, EventReader, EventWriter, Events},
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
lifecycle::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, RemovedComponents},
name::{Name, NameOrEntity},
observer::{Observer, Trigger},
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
related,
relationship::RelationshipTarget,
removal_detection::RemovedComponents,
resource::Resource,
schedule::{
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
@ -93,7 +96,7 @@ pub mod prelude {
},
world::{
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
FromWorld, World,
},
};
@ -123,6 +126,13 @@ pub mod __macro_exports {
pub use alloc::vec::Vec;
}
/// Event sent when a hotpatch happens.
///
/// Systems should refresh their inner pointers.
#[cfg(feature = "hotpatching")]
#[derive(Event, Default)]
pub struct HotPatched;
#[cfg(test)]
mod tests {
use crate::{

View File

@ -0,0 +1,606 @@
//! This module contains various tools to allow you to react to component insertion or removal,
//! as well as entity spawning and despawning.
//!
//! There are four main ways to react to these lifecycle events:
//!
//! 1. Using component hooks, which act as inherent constructors and destructors for components.
//! 2. Using [observers], which are a user-extensible way to respond to events, including component lifecycle events.
//! 3. Using the [`RemovedComponents`] system parameter, which offers an event-style interface.
//! 4. Using the [`Added`] query filter, which checks each component to see if it has been added since the last time a system ran.
//!
//! [observers]: crate::observer
//! [`Added`]: crate::query::Added
//!
//! # Types of lifecycle events
//!
//! There are five types of lifecycle events, split into two categories. First, we have lifecycle events that are triggered
//! when a component is added to an entity:
//!
//! - [`OnAdd`]: Triggered when a component is added to an entity that did not already have it.
//! - [`OnInsert`]: Triggered when a component is added to an entity, regardless of whether it already had it.
//!
//! When both events occur, [`OnAdd`] hooks are evaluated before [`OnInsert`].
//!
//! Next, we have lifecycle events that are triggered when a component is removed from an entity:
//!
//! - [`OnReplace`]: Triggered when a component is removed from an entity, regardless if it is then replaced with a new value.
//! - [`OnRemove`]: Triggered when a component is removed from an entity and not replaced, before the component is removed.
//! - [`OnDespawn`]: Triggered for each component on an entity when it is despawned.
//!
//! [`OnReplace`] hooks are evaluated before [`OnRemove`], then finally [`OnDespawn`] hooks are evaluated.
//!
//! [`OnAdd`] and [`OnRemove`] are counterparts: they are only triggered when a component is added or removed
//! from an entity in such a way as to cause a change in the component's presence on that entity.
//! Similarly, [`OnInsert`] and [`OnReplace`] are counterparts: they are triggered when a component is added or replaced
//! on an entity, regardless of whether this results in a change in the component's presence on that entity.
//!
//! To reliably synchronize data structures using with component lifecycle events,
//! you can combine [`OnInsert`] and [`OnReplace`] to fully capture any changes to the data.
//! This is particularly useful in combination with immutable components,
//! to avoid any lifecycle-bypassing mutations.
//!
//! ## Lifecycle events and component types
//!
//! Despite the absence of generics, each lifecycle event is associated with a specific component.
//! When defining a component hook for a [`Component`] type, that component is used.
//! When listening to lifecycle events for observers, the `B: Bundle` generic is used.
//!
//! Each of these lifecycle events also corresponds to a fixed [`ComponentId`],
//! which are assigned during [`World`] initialization.
//! For example, [`OnAdd`] corresponds to [`ON_ADD`].
//! This is used to skip [`TypeId`](core::any::TypeId) lookups in hot paths.
use crate::{
change_detection::MaybeLocation,
component::{Component, ComponentId, ComponentIdFor, Tick},
entity::Entity,
event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events},
relationship::RelationshipHookMode,
storage::SparseSet,
system::{Local, ReadOnlySystemParam, SystemMeta, SystemParam},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
};
use derive_more::derive::Into;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use core::{
fmt::Debug,
iter,
marker::PhantomData,
ops::{Deref, DerefMut},
option,
};
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext);
/// Context provided to a [`ComponentHook`].
#[derive(Clone, Copy, Debug)]
pub struct HookContext {
/// The [`Entity`] this hook was invoked for.
pub entity: Entity,
/// The [`ComponentId`] this hook was invoked for.
pub component_id: ComponentId,
/// The caller location is `Some` if the `track_caller` feature is enabled.
pub caller: MaybeLocation,
/// Configures how relationship hooks will run
pub relationship_hook_mode: RelationshipHookMode,
}
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
///
/// Hooks are functions that run when a component is added, overwritten, or removed from an entity.
/// These are intended to be used for structural side effects that need to happen when a component is added or removed,
/// and are not intended for general-purpose logic.
///
/// For example, you might use a hook to update a cached index when a component is added,
/// to clean up resources when a component is removed,
/// or to keep hierarchical data structures across entities in sync.
///
/// This information is stored in the [`ComponentInfo`](crate::component::ComponentInfo) of the associated component.
///
/// There are two ways of configuring hooks for a component:
/// 1. Defining the relevant hooks on the [`Component`] implementation
/// 2. Using the [`World::register_component_hooks`] method
///
/// # Example
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_platform::collections::HashSet;
///
/// #[derive(Component)]
/// struct MyTrackedComponent;
///
/// #[derive(Resource, Default)]
/// struct TrackedEntities(HashSet<Entity>);
///
/// let mut world = World::new();
/// world.init_resource::<TrackedEntities>();
///
/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
/// assert!(tracked_component_query.iter(&world).next().is_none());
///
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, context| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.insert(context.entity);
/// });
///
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.remove(&context.entity);
/// });
///
/// let entity = world.spawn(MyTrackedComponent).id();
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(tracked_entities.0.contains(&entity));
///
/// world.despawn(entity);
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(!tracked_entities.0.contains(&entity));
/// ```
#[derive(Debug, Clone, Default)]
pub struct ComponentHooks {
pub(crate) on_add: Option<ComponentHook>,
pub(crate) on_insert: Option<ComponentHook>,
pub(crate) on_replace: Option<ComponentHook>,
pub(crate) on_remove: Option<ComponentHook>,
pub(crate) on_despawn: Option<ComponentHook>,
}
impl ComponentHooks {
pub(crate) fn update_from_component<C: Component + ?Sized>(&mut self) -> &mut Self {
if let Some(hook) = C::on_add() {
self.on_add(hook);
}
if let Some(hook) = C::on_insert() {
self.on_insert(hook);
}
if let Some(hook) = C::on_replace() {
self.on_replace(hook);
}
if let Some(hook) = C::on_remove() {
self.on_remove(hook);
}
if let Some(hook) = C::on_despawn() {
self.on_despawn(hook);
}
self
}
/// Register a [`ComponentHook`] that will be run when this component is added to an entity.
/// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as
/// adding all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
.expect("Component already has an on_add hook")
}
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
/// or replaced.
///
/// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
.expect("Component already has an on_insert hook")
}
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
/// such as being replaced (with `.insert`) or removed.
///
/// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced,
/// allowing access to the previous data just before it is dropped.
/// This hook does *not* run if the entity did not already have this component.
///
/// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_replace` hook
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_replace(hook)
.expect("Component already has an on_replace hook")
}
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
/// Despawning an entity counts as removing all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component already has an on_remove hook")
}
/// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
///
/// # Panics
///
/// Will panic if the component already has an `on_despawn` hook
pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_despawn(hook)
.expect("Component already has an on_despawn hook")
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
///
/// This is a fallible version of [`Self::on_add`].
///
/// Returns `None` if the component already has an `on_add` hook.
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_add.is_some() {
return None;
}
self.on_add = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
///
/// This is a fallible version of [`Self::on_insert`].
///
/// Returns `None` if the component already has an `on_insert` hook.
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_insert.is_some() {
return None;
}
self.on_insert = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed
///
/// This is a fallible version of [`Self::on_replace`].
///
/// Returns `None` if the component already has an `on_replace` hook.
pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_replace.is_some() {
return None;
}
self.on_replace = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity.
///
/// This is a fallible version of [`Self::on_remove`].
///
/// Returns `None` if the component already has an `on_remove` hook.
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_remove.is_some() {
return None;
}
self.on_remove = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
///
/// This is a fallible version of [`Self::on_despawn`].
///
/// Returns `None` if the component already has an `on_despawn` hook.
pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_despawn.is_some() {
return None;
}
self.on_despawn = Some(hook);
Some(self)
}
}
/// [`ComponentId`] for [`OnAdd`]
pub const ON_ADD: ComponentId = ComponentId::new(0);
/// [`ComponentId`] for [`OnInsert`]
pub const ON_INSERT: ComponentId = ComponentId::new(1);
/// [`ComponentId`] for [`OnReplace`]
pub const ON_REPLACE: ComponentId = ComponentId::new(2);
/// [`ComponentId`] for [`OnRemove`]
pub const ON_REMOVE: ComponentId = ComponentId::new(3);
/// [`ComponentId`] for [`OnDespawn`]
pub const ON_DESPAWN: ComponentId = ComponentId::new(4);
/// Trigger emitted when a component is inserted onto an entity that does not already have that
/// component. Runs before `OnInsert`.
/// See [`crate::lifecycle::ComponentHooks::on_add`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnAdd;
/// Trigger emitted when a component is inserted, regardless of whether or not the entity already
/// had that component. Runs after `OnAdd`, if it ran.
/// See [`crate::lifecycle::ComponentHooks::on_insert`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnInsert;
/// Trigger emitted when a component is removed from an entity, regardless
/// of whether or not it is later replaced.
///
/// Runs before the value is replaced, so you can still access the original component data.
/// See [`crate::lifecycle::ComponentHooks::on_replace`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnReplace;
/// Trigger emitted when a component is removed from an entity, and runs before the component is
/// removed, so you can still access the component data.
/// See [`crate::lifecycle::ComponentHooks::on_remove`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnRemove;
/// Trigger emitted for each component on an entity when it is despawned.
/// See [`crate::lifecycle::ComponentHooks::on_despawn`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnDespawn;
/// Wrapper around [`Entity`] for [`RemovedComponents`].
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
#[derive(Event, Debug, Clone, Into)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))]
pub struct RemovedComponentEntity(Entity);
/// Wrapper around a [`EventCursor<RemovedComponentEntity>`] so that we
/// can differentiate events between components.
#[derive(Debug)]
pub struct RemovedComponentReader<T>
where
T: Component,
{
reader: EventCursor<RemovedComponentEntity>,
marker: PhantomData<T>,
}
impl<T: Component> Default for RemovedComponentReader<T> {
fn default() -> Self {
Self {
reader: Default::default(),
marker: PhantomData,
}
}
}
impl<T: Component> Deref for RemovedComponentReader<T> {
type Target = EventCursor<RemovedComponentEntity>;
fn deref(&self) -> &Self::Target {
&self.reader
}
}
impl<T: Component> DerefMut for RemovedComponentReader<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.reader
}
}
/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`].
#[derive(Default, Debug)]
pub struct RemovedComponentEvents {
event_sets: SparseSet<ComponentId, Events<RemovedComponentEntity>>,
}
impl RemovedComponentEvents {
/// Creates an empty storage buffer for component removal events.
pub fn new() -> Self {
Self::default()
}
/// For each type of component, swaps the event buffers and clears the oldest event buffer.
/// In general, this should be called once per frame/update.
pub fn update(&mut self) {
for (_component_id, events) in self.event_sets.iter_mut() {
events.update();
}
}
/// Returns an iterator over components and their entity events.
pub fn iter(&self) -> impl Iterator<Item = (&ComponentId, &Events<RemovedComponentEntity>)> {
self.event_sets.iter()
}
/// Gets the event storage for a given component.
pub fn get(
&self,
component_id: impl Into<ComponentId>,
) -> Option<&Events<RemovedComponentEntity>> {
self.event_sets.get(component_id.into())
}
/// Sends a removal event for the specified component.
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
self.event_sets
.get_or_insert_with(component_id.into(), Default::default)
.send(RemovedComponentEntity(entity));
}
}
/// A [`SystemParam`] that yields entities that had their `T` [`Component`]
/// removed or have been despawned with it.
///
/// This acts effectively the same as an [`EventReader`](crate::event::EventReader).
///
/// Unlike hooks or observers (see the [lifecycle](crate) module docs),
/// this does not allow you to see which data existed before removal.
///
/// If you are using `bevy_ecs` as a standalone crate,
/// note that the [`RemovedComponents`] list will not be automatically cleared for you,
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers).
///
/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is
/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`.
/// For the main world, this is delayed until after all `SubApp`s have run.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::lifecycle::RemovedComponents;
/// #
/// # #[derive(Component)]
/// # struct MyComponent;
/// fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
/// removed.read().for_each(|removed_entity| println!("{}", removed_entity));
/// }
/// # bevy_ecs::system::assert_is_system(react_on_removal);
/// ```
#[derive(SystemParam)]
pub struct RemovedComponents<'w, 's, T: Component> {
component_id: ComponentIdFor<'s, T>,
reader: Local<'s, RemovedComponentReader<T>>,
event_sets: &'w RemovedComponentEvents,
}
/// Iterator over entities that had a specific component removed.
///
/// See [`RemovedComponents`].
pub type RemovedIter<'a> = iter::Map<
iter::Flatten<option::IntoIter<iter::Cloned<EventIterator<'a, RemovedComponentEntity>>>>,
fn(RemovedComponentEntity) -> Entity,
>;
/// Iterator over entities that had a specific component removed.
///
/// See [`RemovedComponents`].
pub type RemovedIterWithId<'a> = iter::Map<
iter::Flatten<option::IntoIter<EventIteratorWithId<'a, RemovedComponentEntity>>>,
fn(
(&RemovedComponentEntity, EventId<RemovedComponentEntity>),
) -> (Entity, EventId<RemovedComponentEntity>),
>;
fn map_id_events(
(entity, id): (&RemovedComponentEntity, EventId<RemovedComponentEntity>),
) -> (Entity, EventId<RemovedComponentEntity>) {
(entity.clone().into(), id)
}
// For all practical purposes, the api surface of `RemovedComponents<T>`
// should be similar to `EventReader<T>` to reduce confusion.
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
/// Fetch underlying [`EventCursor`].
pub fn reader(&self) -> &EventCursor<RemovedComponentEntity> {
&self.reader
}
/// Fetch underlying [`EventCursor`] mutably.
pub fn reader_mut(&mut self) -> &mut EventCursor<RemovedComponentEntity> {
&mut self.reader
}
/// Fetch underlying [`Events`].
pub fn events(&self) -> Option<&Events<RemovedComponentEntity>> {
self.event_sets.get(self.component_id.get())
}
/// Destructures to get a mutable reference to the `EventCursor`
/// and a reference to `Events`.
///
/// This is necessary since Rust can't detect destructuring through methods and most
/// usecases of the reader uses the `Events` as well.
pub fn reader_mut_with_events(
&mut self,
) -> Option<(
&mut RemovedComponentReader<T>,
&Events<RemovedComponentEntity>,
)> {
self.event_sets
.get(self.component_id.get())
.map(|events| (&mut *self.reader, events))
}
/// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the
/// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events
/// that happened before now.
pub fn read(&mut self) -> RemovedIter<'_> {
self.reader_mut_with_events()
.map(|(reader, events)| reader.read(events).cloned())
.into_iter()
.flatten()
.map(RemovedComponentEntity::into)
}
/// Like [`read`](Self::read), except also returning the [`EventId`] of the events.
pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> {
self.reader_mut_with_events()
.map(|(reader, events)| reader.read_with_id(events))
.into_iter()
.flatten()
.map(map_id_events)
}
/// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any.
pub fn len(&self) -> usize {
self.events()
.map(|events| self.reader.len(events))
.unwrap_or(0)
}
/// Returns `true` if there are no events available to read.
pub fn is_empty(&self) -> bool {
self.events()
.is_none_or(|events| self.reader.is_empty(events))
}
/// Consumes all available events.
///
/// This means these events will not appear in calls to [`RemovedComponents::read()`] or
/// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`.
pub fn clear(&mut self) {
if let Some((reader, events)) = self.reader_mut_with_events() {
reader.clear(events);
}
}
}
// SAFETY: Only reads World removed component events
unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}
// SAFETY: no component value access.
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
type State = ();
type Item<'w, 's> = &'w RemovedComponentEvents;
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
#[inline]
unsafe fn get_param<'w, 's>(
_state: &'s mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
_change_tick: Tick,
) -> Self::Item<'w, 's> {
world.removed_components()
}
}

View File

@ -1,8 +1,7 @@
use crate::{
component::{
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
},
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
lifecycle::{ComponentHook, HookContext},
world::World,
};
use alloc::vec::Vec;

View File

@ -1,4 +1,136 @@
//! Types for creating and storing [`Observer`]s
//! Observers are a push-based tool for responding to [`Event`]s.
//!
//! ## Observer targeting
//!
//! Observers can be "global", listening for events that are both targeted at and not targeted at any specific entity,
//! or they can be "entity-specific", listening for events that are targeted at specific entities.
//!
//! They can also be further refined by listening to events targeted at specific components
//! (instead of using a generic event type), as is done with the [`OnAdd`] family of lifecycle events.
//!
//! When entities are observed, they will receive an [`ObservedBy`] component,
//! which will be updated to track the observers that are currently observing them.
//!
//! Currently, [observers cannot be retargeted after spawning](https://github.com/bevyengine/bevy/issues/19587):
//! despawn and respawn an observer as a workaround.
//!
//! ## Writing observers
//!
//! Observers are systems which implement [`IntoObserverSystem`] that listen for [`Event`]s matching their
//! type and target(s).
//! To write observer systems, use the [`Trigger`] system parameter as the first parameter of your system.
//! This parameter provides access to the specific event that triggered the observer,
//! as well as the entity that the event was targeted at, if any.
//!
//! Observers can request other data from the world,
//! such as via a [`Query`] or [`Res`]. Commonly, you might want to verify that
//! the entity that the observable event is targeting has a specific component,
//! or meets some other condition.
//! [`Query::get`] or [`Query::contains`] on the [`Trigger::target`] entity
//! is a good way to do this.
//!
//! [`Commands`] can also be used inside of observers.
//! This can be particularly useful for triggering other observers!
//!
//! ## Spawning observers
//!
//! Observers can be spawned via [`World::add_observer`], or the equivalent app method.
//! This will cause an entity with the [`Observer`] component to be created,
//! which will then run the observer system whenever the event it is watching is triggered.
//!
//! You can control the targets that an observer is watching by calling [`Observer::watch_entity`]
//! once the entity is spawned, or by manually spawning an entity with the [`Observer`] component
//! configured with the desired targets.
//!
//! Observers are fundamentally defined as "entities which have the [`Observer`] component"
//! allowing you to add it manually to existing entities.
//! At first, this seems convenient, but only one observer can be added to an entity at a time,
//! regardless of the event it responds to: like always, components are unique.
//!
//! Instead, a better way to achieve a similar aim is to
//! use the [`EntityWorldMut::observe`] / [`EntityCommands::observe`] method,
//! which spawns a new observer, and configures it to watch the entity it is called on.
//! Unfortunately, observers defined in this way
//! [currently cannot be spawned as part of bundles](https://github.com/bevyengine/bevy/issues/14204).
//!
//! ## Triggering observers
//!
//! Observers are most commonly triggered by [`Commands`],
//! via [`Commands::trigger`] (for untargeted events) or [`Commands::trigger_targets`] (for targeted events).
//! Like usual, equivalent methods are available on [`World`], allowing you to reduce overhead when working with exclusive world access.
//!
//! If your observer is configured to watch for a specific component or set of components instead,
//! you can pass in [`ComponentId`]s into [`Commands::trigger_targets`] by using the [`TriggerTargets`] trait.
//! As discussed in the [`Trigger`] documentation, this use case is rare, and is currently only used
//! for [lifecycle](crate::lifecycle) events, which are automatically emitted.
//!
//! ## Observer bubbling
//!
//! When events are targeted at an entity, they can optionally bubble to other targets,
//! typically up to parents in an entity hierarchy.
//!
//! This behavior is controlled via [`Event::Traversal`] and [`Event::AUTO_PROPAGATE`],
//! with the details of the propagation path specified by the [`Traversal`](crate::traversal::Traversal) trait.
//!
//! When auto-propagation is enabled, propagaion must be manually stopped to prevent the event from
//! continuing to other targets.
//! This can be done using the [`Trigger::propagate`] method on the [`Trigger`] system parameter inside of your observer.
//!
//! ## Observer timing
//!
//! Observers are triggered via [`Commands`], which imply that they are evaluated at the next sync point in the ECS schedule.
//! Accordingly, they have full access to the world, and are evaluated sequentially, in the order that the commands were sent.
//!
//! To control the relative ordering of observers sent from different systems,
//! order the systems in the schedule relative to each other.
//!
//! Currently, Bevy does not provide [a way to specify the ordering of observers](https://github.com/bevyengine/bevy/issues/14890)
//! listening to the same event relative to each other.
//!
//! Commands sent by observers are [currently not immediately applied](https://github.com/bevyengine/bevy/issues/19569).
//! Instead, all queued observers will run, and then all of the commands from those observers will be applied.
//! Careful use of [`Schedule::apply_deferred`] may help as a workaround.
//!
//! ## Lifecycle events and observers
//!
//! It is important to note that observers, just like [hooks](crate::lifecycle::ComponentHooks),
//! can listen to and respond to [lifecycle](crate::lifecycle) events.
//! Unlike hooks, observers are not treated as an "innate" part of component behavior:
//! they can be added or removed at runtime, and multiple observers
//! can be registered for the same lifecycle event for the same component.
//!
//! The ordering of hooks versus observers differs based on the lifecycle event in question:
//!
//! - when adding components, hooks are evaluated first, then observers
//! - when removing components, observers are evaluated first, then hooks
//!
//! This allows hooks to act as constructors and destructors for components,
//! as they always have the first and final say in the component's lifecycle.
//!
//! ## Cleaning up observers
//!
//! Currently, observer entities are never cleaned up, even if their target entity(s) are despawned.
//! This won't cause any runtime overhead, but is a waste of memory and can result in memory leaks.
//!
//! If you run into this problem, you could manually scan the world for observer entities and despawn them,
//! by checking if the entity in [`Observer::descriptor`] still exists.
//!
//! ## Observers vs buffered events
//!
//! By contrast, [`EventReader`] and [`EventWriter`] ("buffered events"), are pull-based.
//! They require periodically polling the world to check for new events, typically in a system that runs as part of a schedule.
//!
//! This imposes a small overhead, making observers a better choice for extremely rare events,
//! but buffered events can be more efficient for events that are expected to occur multiple times per frame,
//! as it allows for batch processing of events.
//!
//! The difference in timing is also an important consideration:
//! buffered events are evaluated at fixed points during schedules,
//! while observers are evaluated as soon as possible after the event is triggered.
//!
//! This provides more control over the timing of buffered event evaluation,
//! but allows for a more ad hoc approach with observers,
//! and enables indefinite chaining of observers triggering other observers (for both better and worse!).
mod entity_observer;
mod runner;
@ -29,6 +161,17 @@ use smallvec::SmallVec;
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
/// contains event propagation information. See [`Trigger::propagate`] for more information.
///
/// The generic `B: Bundle` is used to modify the further specialize the events that this observer is interested in.
/// The entity involved *does not* have to have these components, but the observer will only be
/// triggered if the event matches the components in `B`.
///
/// This is used to to avoid providing a generic argument in your event, as is done for [`OnAdd`]
/// and the other lifecycle events.
///
/// Providing multiple components in this bundle will cause this event to be triggered by any
/// matching component in the bundle,
/// [rather than requiring all of them to be present](https://github.com/bevyengine/bevy/issues/15325).
pub struct Trigger<'w, E, B: Bundle = ()> {
event: &'w mut E,
propagate: &'w mut bool,
@ -68,20 +211,8 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> {
}
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. It may
/// be [`Entity::PLACEHOLDER`].
///
/// Observable events can target specific entities. When those events fire, they will trigger
/// any observers on the targeted entities. In this case, the `target()` and `observer()` are
/// the same, because the observer that was triggered is attached to the entity that was
/// targeted by the event.
///
/// However, it is also possible for those events to bubble up the entity hierarchy and trigger
/// observers on *different* entities, or trigger a global observer. In these cases, the
/// observing entity is *different* from the entity being targeted by the event.
///
/// This is an important distinction: the entity reacting to an event is not always the same as
/// the entity triggered by the event.
pub fn target(&self) -> Entity {
/// be [`None`] if the trigger is not for a particular entity.
pub fn target(&self) -> Option<Entity> {
self.trigger.target
}
@ -172,10 +303,14 @@ impl<'w, E, B: Bundle> DerefMut for Trigger<'w, E, B> {
}
}
/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`]. Targets can be of type [`Entity`] or [`ComponentId`].
/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`].
///
/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination
/// will run.
///
/// This trait is implemented for both [`Entity`] and [`ComponentId`], allowing you to target specific entities or components.
/// It is also implemented for various collections of these types, such as [`Vec`], arrays, and tuples,
/// allowing you to trigger events for multiple targets at once.
pub trait TriggerTargets {
/// The components the trigger should target.
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_;
@ -280,7 +415,9 @@ all_tuples!(
T
);
/// A description of what an [`Observer`] observes.
/// Store information about what an [`Observer`] observes.
///
/// This information is stored inside of the [`Observer`] component,
#[derive(Default, Clone)]
pub struct ObserverDescriptor {
/// The events the observer is watching.
@ -331,7 +468,9 @@ impl ObserverDescriptor {
}
}
/// Event trigger metadata for a given [`Observer`],
/// Metadata about a specific [`Event`] which triggered an observer.
///
/// This information is exposed via methods on the [`Trigger`] system parameter.
#[derive(Debug)]
pub struct ObserverTrigger {
/// The [`Entity`] of the observer handling the trigger.
@ -341,7 +480,7 @@ pub struct ObserverTrigger {
/// The [`ComponentId`]s the trigger targeted.
components: SmallVec<[ComponentId; 2]>,
/// The entity the trigger targeted.
pub target: Entity,
pub target: Option<Entity>,
/// The location of the source code that triggered the observer.
pub caller: MaybeLocation,
}
@ -357,6 +496,8 @@ impl ObserverTrigger {
type ObserverMap = EntityHashMap<ObserverRunner>;
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component.
///
/// This is stored inside of [`CachedObservers`].
#[derive(Default, Debug)]
pub struct CachedComponentObservers {
// Observers listening to triggers targeting this component
@ -366,6 +507,8 @@ pub struct CachedComponentObservers {
}
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger.
///
/// This is stored inside of [`Observers`], specialized for each kind of observer.
#[derive(Default, Debug)]
pub struct CachedObservers {
// Observers listening for any time this trigger is fired
@ -376,7 +519,13 @@ pub struct CachedObservers {
entity_observers: EntityHashMap<ObserverMap>,
}
/// Metadata for observers. Stores a cache mapping trigger ids to the registered observers.
/// An internal lookup table tracking all of the observers in the world.
///
/// Stores a cache mapping trigger ids to the registered observers.
/// Some observer kinds (like [lifecycle](crate::lifecycle) observers) have a dedicated field,
/// saving lookups for the most common triggers.
///
/// This is stored as a field of the [`World`].
#[derive(Default, Debug)]
pub struct Observers {
// Cached ECS observers to save a lookup most common triggers.
@ -385,12 +534,14 @@ pub struct Observers {
on_replace: CachedObservers,
on_remove: CachedObservers,
on_despawn: CachedObservers,
// Map from trigger type to set of observers
// Map from trigger type to set of observers listening to that trigger
cache: HashMap<ComponentId, CachedObservers>,
}
impl Observers {
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
use crate::lifecycle::*;
match event_type {
ON_ADD => &mut self.on_add,
ON_INSERT => &mut self.on_insert,
@ -402,6 +553,8 @@ impl Observers {
}
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
use crate::lifecycle::*;
match event_type {
ON_ADD => Some(&self.on_add),
ON_INSERT => Some(&self.on_insert),
@ -416,7 +569,7 @@ impl Observers {
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
target: Entity,
target: Option<Entity>,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T,
propagate: &mut bool,
@ -455,8 +608,8 @@ impl Observers {
observers.map.iter().for_each(&mut trigger_observer);
// Trigger entity observers listening for this kind of trigger
if target != Entity::PLACEHOLDER {
if let Some(map) = observers.entity_observers.get(&target) {
if let Some(target_entity) = target {
if let Some(map) = observers.entity_observers.get(&target_entity) {
map.iter().for_each(&mut trigger_observer);
}
}
@ -469,8 +622,8 @@ impl Observers {
.iter()
.for_each(&mut trigger_observer);
if target != Entity::PLACEHOLDER {
if let Some(map) = component_observers.entity_map.get(&target) {
if let Some(target_entity) = target {
if let Some(map) = component_observers.entity_map.get(&target_entity) {
map.iter().for_each(&mut trigger_observer);
}
}
@ -479,6 +632,8 @@ impl Observers {
}
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
use crate::lifecycle::*;
match event_type {
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
@ -695,7 +850,7 @@ impl World {
unsafe {
world.trigger_observers_with_data::<_, E::Traversal>(
event_id,
Entity::PLACEHOLDER,
None,
targets.components(),
event_data,
false,
@ -708,7 +863,7 @@ impl World {
unsafe {
world.trigger_observers_with_data::<_, E::Traversal>(
event_id,
target_entity,
Some(target_entity),
targets.components(),
event_data,
E::AUTO_PROPAGATE,
@ -999,20 +1154,20 @@ mod tests {
world.add_observer(
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_a");
commands.entity(obs.target()).insert(B);
commands.entity(obs.target().unwrap()).insert(B);
},
);
world.add_observer(
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("remove_a");
commands.entity(obs.target()).remove::<B>();
commands.entity(obs.target().unwrap()).remove::<B>();
},
);
world.add_observer(
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
res.observed("add_b");
commands.entity(obs.target()).remove::<A>();
commands.entity(obs.target().unwrap()).remove::<A>();
},
);
world.add_observer(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
@ -1181,7 +1336,7 @@ mod tests {
};
world.spawn_empty().observe(system);
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.target(), Entity::PLACEHOLDER);
assert_eq!(obs.target(), None);
res.observed("event_a");
});
@ -1208,7 +1363,7 @@ mod tests {
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
.id();
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
assert_eq!(obs.target(), entity);
assert_eq!(obs.target().unwrap(), entity);
res.observed("a_2");
});
@ -1628,7 +1783,7 @@ mod tests {
world.add_observer(
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
if query.get(trigger.target()).is_ok() {
if query.get(trigger.target().unwrap()).is_ok() {
res.observed("event");
}
},
@ -1651,7 +1806,7 @@ mod tests {
fn observer_modifies_relationship() {
fn on_add(trigger: Trigger<OnAdd, A>, mut commands: Commands) {
commands
.entity(trigger.target())
.entity(trigger.target().unwrap())
.with_related_entities::<crate::hierarchy::ChildOf>(|rsc| {
rsc.spawn_empty();
});

View File

@ -2,8 +2,9 @@ use alloc::{boxed::Box, vec};
use core::any::Any;
use crate::{
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
component::{ComponentId, Mutable, StorageType},
error::{ErrorContext, ErrorHandler},
lifecycle::{ComponentHook, HookContext},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
@ -123,8 +124,8 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// struct Explode;
///
/// world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("Entity {} goes BOOM!", trigger.target());
/// commands.entity(trigger.target()).despawn();
/// println!("Entity {} goes BOOM!", trigger.target().unwrap());
/// commands.entity(trigger.target().unwrap()).despawn();
/// });
///
/// world.flush();
@ -157,7 +158,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # struct Explode;
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.target()).despawn();
/// commands.entity(trigger.target().unwrap()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
@ -371,6 +372,11 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
// and is never exclusive
// - system is the same type erased system from above
unsafe {
// Always refresh hotpatch pointers
// There's no guarantee that the `HotPatched` event would still be there once the observer is triggered.
#[cfg(feature = "hotpatching")]
(*system).refresh_hotpatch();
match (*system).validate_param_unsafe(world) {
Ok(()) => {
if let Err(err) = (*system).run_unsafe(trigger, world) {
@ -405,7 +411,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
}
}
/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::component::ComponentHooks::on_add`).
/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::lifecycle::ComponentHooks::on_add`).
///
/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters
/// erased.

View File

@ -47,6 +47,8 @@ use variadics_please::all_tuples;
/// - **[`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.
/// - **[`Mut`].**
/// Mutable component access, with change detection data.
/// - **[`Has`].**
/// Returns a bool indicating whether the entity has the specified component.
///

View File

@ -11,10 +11,10 @@ pub use relationship_query::*;
pub use relationship_source_collection::*;
use crate::{
component::{Component, HookContext, Mutable},
component::{Component, Mutable},
entity::{ComponentCloneCtx, Entity, SourceComponent},
error::{ignore, CommandWithEntity, HandleError},
system::entity_command::{self},
lifecycle::HookContext,
world::{DeferredWorld, EntityWorldMut},
};
use log::warn;
@ -223,50 +223,24 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
// note: think of this as "on_drop"
fn on_replace(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
fn on_replace(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let (entities, mut commands) = world.entities_and_commands();
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
for source_entity in relationship_target.iter() {
if entities.get(source_entity).is_ok() {
commands.queue(
entity_command::remove::<Self::Relationship>()
.with_entity(source_entity)
.handle_error_with(ignore),
);
} else {
warn!(
"{}Tried to despawn non-existent entity {}",
caller
.map(|location| format!("{location}: "))
.unwrap_or_default(),
source_entity
);
}
commands
.entity(source_entity)
.remove::<Self::Relationship>();
}
}
/// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
/// that entity is despawned.
// note: think of this as "on_drop"
fn on_despawn(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
fn on_despawn(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let (entities, mut commands) = world.entities_and_commands();
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
for source_entity in relationship_target.iter() {
if entities.get(source_entity).is_ok() {
commands.queue(
entity_command::despawn()
.with_entity(source_entity)
.handle_error_with(ignore),
);
} else {
warn!(
"{}Tried to despawn non-existent entity {}",
caller
.map(|location| format!("{location}: "))
.unwrap_or_default(),
source_entity
);
}
commands.entity(source_entity).despawn();
}
}

View File

@ -1,6 +1,7 @@
use crate::{
bundle::Bundle,
entity::{hash_set::EntityHashSet, Entity},
prelude::Children,
relationship::{
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
},
@ -302,6 +303,15 @@ impl<'w> EntityWorldMut<'w> {
self
}
/// Despawns the children of this entity.
/// This entity will not be despawned.
///
/// This is a specialization of [`despawn_related`](EntityWorldMut::despawn_related), a more general method for despawning via relationships.
pub fn despawn_children(&mut self) -> &mut Self {
self.despawn_related::<Children>();
self
}
/// Inserts a component or bundle of components into the entity and all related entities,
/// traversing the relationship tracked in `S` in a breadth-first manner.
///
@ -467,6 +477,14 @@ impl<'a> EntityCommands<'a> {
})
}
/// Despawns the children of this entity.
/// This entity will not be despawned.
///
/// This is a specialization of [`despawn_related`](EntityCommands::despawn_related), a more general method for despawning via relationships.
pub fn despawn_children(&mut self) -> &mut Self {
self.despawn_related::<Children>()
}
/// Inserts a component or bundle of components into the entity and all related entities,
/// traversing the relationship tracked in `S` in a breadth-first manner.
///

View File

@ -1,268 +0,0 @@
//! Alerting events when a component is removed from an entity.
use crate::{
component::{Component, ComponentId, ComponentIdFor, Tick},
entity::Entity,
event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events},
prelude::Local,
storage::SparseSet,
system::{ReadOnlySystemParam, SystemMeta, SystemParam},
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use derive_more::derive::Into;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use core::{
fmt::Debug,
iter,
marker::PhantomData,
ops::{Deref, DerefMut},
option,
};
/// Wrapper around [`Entity`] for [`RemovedComponents`].
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
#[derive(Event, Debug, Clone, Into)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))]
pub struct RemovedComponentEntity(Entity);
/// Wrapper around a [`EventCursor<RemovedComponentEntity>`] so that we
/// can differentiate events between components.
#[derive(Debug)]
pub struct RemovedComponentReader<T>
where
T: Component,
{
reader: EventCursor<RemovedComponentEntity>,
marker: PhantomData<T>,
}
impl<T: Component> Default for RemovedComponentReader<T> {
fn default() -> Self {
Self {
reader: Default::default(),
marker: PhantomData,
}
}
}
impl<T: Component> Deref for RemovedComponentReader<T> {
type Target = EventCursor<RemovedComponentEntity>;
fn deref(&self) -> &Self::Target {
&self.reader
}
}
impl<T: Component> DerefMut for RemovedComponentReader<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.reader
}
}
/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`].
#[derive(Default, Debug)]
pub struct RemovedComponentEvents {
event_sets: SparseSet<ComponentId, Events<RemovedComponentEntity>>,
}
impl RemovedComponentEvents {
/// Creates an empty storage buffer for component removal events.
pub fn new() -> Self {
Self::default()
}
/// For each type of component, swaps the event buffers and clears the oldest event buffer.
/// In general, this should be called once per frame/update.
pub fn update(&mut self) {
for (_component_id, events) in self.event_sets.iter_mut() {
events.update();
}
}
/// Returns an iterator over components and their entity events.
pub fn iter(&self) -> impl Iterator<Item = (&ComponentId, &Events<RemovedComponentEntity>)> {
self.event_sets.iter()
}
/// Gets the event storage for a given component.
pub fn get(
&self,
component_id: impl Into<ComponentId>,
) -> Option<&Events<RemovedComponentEntity>> {
self.event_sets.get(component_id.into())
}
/// Sends a removal event for the specified component.
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
self.event_sets
.get_or_insert_with(component_id.into(), Default::default)
.send(RemovedComponentEntity(entity));
}
}
/// A [`SystemParam`] that yields entities that had their `T` [`Component`]
/// removed or have been despawned with it.
///
/// This acts effectively the same as an [`EventReader`](crate::event::EventReader).
///
/// Note that this does not allow you to see which data existed before removal.
/// If you need this, you will need to track the component data value on your own,
/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed<T>>`
/// and stores the data somewhere safe to later cross-reference.
///
/// If you are using `bevy_ecs` as a standalone crate,
/// note that the `RemovedComponents` list will not be automatically cleared for you,
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers).
///
/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is
/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`.
/// For the main world, this is delayed until after all `SubApp`s have run.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::removal_detection::RemovedComponents;
/// #
/// # #[derive(Component)]
/// # struct MyComponent;
/// fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
/// removed.read().for_each(|removed_entity| println!("{}", removed_entity));
/// }
/// # bevy_ecs::system::assert_is_system(react_on_removal);
/// ```
#[derive(SystemParam)]
pub struct RemovedComponents<'w, 's, T: Component> {
component_id: ComponentIdFor<'s, T>,
reader: Local<'s, RemovedComponentReader<T>>,
event_sets: &'w RemovedComponentEvents,
}
/// Iterator over entities that had a specific component removed.
///
/// See [`RemovedComponents`].
pub type RemovedIter<'a> = iter::Map<
iter::Flatten<option::IntoIter<iter::Cloned<EventIterator<'a, RemovedComponentEntity>>>>,
fn(RemovedComponentEntity) -> Entity,
>;
/// Iterator over entities that had a specific component removed.
///
/// See [`RemovedComponents`].
pub type RemovedIterWithId<'a> = iter::Map<
iter::Flatten<option::IntoIter<EventIteratorWithId<'a, RemovedComponentEntity>>>,
fn(
(&RemovedComponentEntity, EventId<RemovedComponentEntity>),
) -> (Entity, EventId<RemovedComponentEntity>),
>;
fn map_id_events(
(entity, id): (&RemovedComponentEntity, EventId<RemovedComponentEntity>),
) -> (Entity, EventId<RemovedComponentEntity>) {
(entity.clone().into(), id)
}
// For all practical purposes, the api surface of `RemovedComponents<T>`
// should be similar to `EventReader<T>` to reduce confusion.
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
/// Fetch underlying [`EventCursor`].
pub fn reader(&self) -> &EventCursor<RemovedComponentEntity> {
&self.reader
}
/// Fetch underlying [`EventCursor`] mutably.
pub fn reader_mut(&mut self) -> &mut EventCursor<RemovedComponentEntity> {
&mut self.reader
}
/// Fetch underlying [`Events`].
pub fn events(&self) -> Option<&Events<RemovedComponentEntity>> {
self.event_sets.get(self.component_id.get())
}
/// Destructures to get a mutable reference to the `EventCursor`
/// and a reference to `Events`.
///
/// This is necessary since Rust can't detect destructuring through methods and most
/// usecases of the reader uses the `Events` as well.
pub fn reader_mut_with_events(
&mut self,
) -> Option<(
&mut RemovedComponentReader<T>,
&Events<RemovedComponentEntity>,
)> {
self.event_sets
.get(self.component_id.get())
.map(|events| (&mut *self.reader, events))
}
/// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the
/// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events
/// that happened before now.
pub fn read(&mut self) -> RemovedIter<'_> {
self.reader_mut_with_events()
.map(|(reader, events)| reader.read(events).cloned())
.into_iter()
.flatten()
.map(RemovedComponentEntity::into)
}
/// Like [`read`](Self::read), except also returning the [`EventId`] of the events.
pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> {
self.reader_mut_with_events()
.map(|(reader, events)| reader.read_with_id(events))
.into_iter()
.flatten()
.map(map_id_events)
}
/// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any.
pub fn len(&self) -> usize {
self.events()
.map(|events| self.reader.len(events))
.unwrap_or(0)
}
/// Returns `true` if there are no events available to read.
pub fn is_empty(&self) -> bool {
self.events()
.is_none_or(|events| self.reader.is_empty(events))
}
/// Consumes all available events.
///
/// This means these events will not appear in calls to [`RemovedComponents::read()`] or
/// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`.
pub fn clear(&mut self) {
if let Some((reader, events)) = self.reader_mut_with_events() {
reader.clear(events);
}
}
}
// SAFETY: Only reads World removed component events
unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}
// SAFETY: no component value access.
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
type State = ();
type Item<'w, 's> = &'w RemovedComponentEvents;
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
#[inline]
unsafe fn get_param<'w, 's>(
_state: &'s mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
_change_tick: Tick,
) -> Self::Item<'w, 's> {
world.removed_components()
}
}

View File

@ -11,8 +11,18 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// A system that determines if one or more scheduled systems should run.
///
/// Implemented for functions and closures that convert into [`System<Out=bool>`](System)
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
/// `SystemCondition` is sealed and implemented for functions and closures with
/// [read-only](crate::system::ReadOnlySystemParam) parameters that convert into
/// [`System<Out = bool>`](System), [`System<Out = Result<(), BevyError>>`](System) or
/// [`System<Out = Result<bool, BevyError>>`](System).
///
/// `SystemCondition` offers a private method
/// (called by [`run_if`](crate::schedule::IntoScheduleConfigs::run_if) and the provided methods)
/// that converts the implementing system into a condition (system) returning a bool.
/// Depending on the output type of the implementing system:
/// - `bool`: the implementing system is used as the condition;
/// - `Result<(), BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(())`;
/// - `Result<bool, BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(true)`.
///
/// # Marker type parameter
///
@ -31,7 +41,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// ```
///
/// # Examples
/// A condition that returns true every other time it's called.
/// A condition that returns `true` every other time it's called.
/// ```
/// # use bevy_ecs::prelude::*;
/// fn every_other_time() -> impl SystemCondition<()> {
@ -54,7 +64,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// # assert!(!world.resource::<DidRun>().0);
/// ```
///
/// A condition that takes a bool as an input and returns it unchanged.
/// A condition that takes a `bool` as an input and returns it unchanged.
///
/// ```
/// # use bevy_ecs::prelude::*;
@ -71,8 +81,30 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// # world.insert_resource(DidRun(false));
/// # app.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait SystemCondition<Marker, In: SystemInput = ()>:
sealed::SystemCondition<Marker, In>
/// ```
///
/// A condition returning a `Result<(), BevyError>`
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component)] struct Player;
/// fn player_exists(q_player: Query<(), With<Player>>) -> Result {
/// Ok(q_player.single()?)
/// }
///
/// # let mut app = Schedule::default();
/// # #[derive(Resource)] struct DidRun(bool);
/// # fn my_system(mut did_run: ResMut<DidRun>) { did_run.0 = true; }
/// app.add_systems(my_system.run_if(player_exists));
/// # let mut world = World::new();
/// # world.insert_resource(DidRun(false));
/// # app.run(&mut world);
/// # assert!(!world.resource::<DidRun>().0);
/// # world.spawn(Player);
/// # app.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait SystemCondition<Marker, In: SystemInput = (), Out = bool>:
sealed::SystemCondition<Marker, In, Out>
{
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `and` return `true`.
@ -371,28 +403,61 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
}
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F where
F: sealed::SystemCondition<Marker, In>
impl<Marker, In: SystemInput, Out, F> SystemCondition<Marker, In, Out> for F where
F: sealed::SystemCondition<Marker, In, Out>
{
}
mod sealed {
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
use crate::{
error::BevyError,
system::{IntoSystem, ReadOnlySystem, SystemInput},
};
pub trait SystemCondition<Marker, In: SystemInput>:
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
pub trait SystemCondition<Marker, In: SystemInput, Out>:
IntoSystem<In, Out, Marker, System = Self::ReadOnlySystem>
{
// This associated type is necessary to let the compiler
// know that `Self::System` is `ReadOnlySystem`.
type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
type ReadOnlySystem: ReadOnlySystem<In = In, Out = Out>;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool>;
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, bool> for F
where
F: IntoSystem<In, bool, Marker>,
F::System: ReadOnlySystem,
{
type ReadOnlySystem = F::System;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
IntoSystem::into_system(self)
}
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<(), BevyError>> for F
where
F: IntoSystem<In, Result<(), BevyError>, Marker>,
F::System: ReadOnlySystem,
{
type ReadOnlySystem = F::System;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
IntoSystem::into_system(self.map(|result| result.is_ok()))
}
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<bool, BevyError>> for F
where
F: IntoSystem<In, Result<bool, BevyError>, Marker>,
F::System: ReadOnlySystem,
{
type ReadOnlySystem = F::System;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
IntoSystem::into_system(self.map(|result| matches!(result, Ok(true))))
}
}
}
@ -402,9 +467,9 @@ pub mod common_conditions {
use crate::{
change_detection::DetectChanges,
event::{Event, EventReader},
lifecycle::RemovedComponents,
prelude::{Component, Query, With},
query::QueryFilter,
removal_detection::RemovedComponents,
resource::Resource,
system::{In, IntoSystem, Local, Res, System, SystemInput},
};

View File

@ -14,8 +14,8 @@ use crate::{
system::{BoxedSystem, InfallibleSystemWrapper, IntoSystem, ScheduleSystem, System},
};
fn new_condition<M>(condition: impl SystemCondition<M>) -> BoxedCondition {
let condition_system = IntoSystem::into_system(condition);
fn new_condition<M, Out>(condition: impl SystemCondition<M, (), Out>) -> BoxedCondition {
let condition_system = condition.into_condition_system();
assert!(
condition_system.is_send(),
"SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.",
@ -447,7 +447,7 @@ pub trait IntoScheduleConfigs<T: Schedulable<Metadata = GraphInfo, GroupMetadata
///
/// Use [`distributive_run_if`](IntoScheduleConfigs::distributive_run_if) if you want the
/// condition to be evaluated for each individual system, right before one is run.
fn run_if<M>(self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
fn run_if<M, Out>(self, condition: impl SystemCondition<M, (), Out>) -> ScheduleConfigs<T> {
self.into_configs().run_if(condition)
}
@ -535,7 +535,7 @@ impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleCo
self
}
fn run_if<M>(mut self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
fn run_if<M, Out>(mut self, condition: impl SystemCondition<M, (), Out>) -> ScheduleConfigs<T> {
self.run_if_dyn(new_condition(condition));
self
}

View File

@ -18,9 +18,9 @@ use crate::{
component::{ComponentId, Tick},
error::{BevyError, ErrorContext, Result},
prelude::{IntoSystemSet, SystemSet},
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError},
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
};
@ -162,35 +162,14 @@ impl System for ApplyDeferred {
Cow::Borrowed("bevy_ecs::apply_deferred")
}
fn component_access(&self) -> &Access<ComponentId> {
// This system accesses no components.
const { &Access::new() }
}
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
// This system accesses no components.
const { &FilteredAccessSet::new() }
}
fn is_send(&self) -> bool {
// Although this system itself does nothing on its own, the system
// executor uses it to apply deferred commands. Commands must be allowed
// to access non-send resources, so this system must be non-send for
// scheduling purposes.
false
}
fn is_exclusive(&self) -> bool {
// This system is labeled exclusive because it is used by the system
// executor to find places where deferred commands should be applied,
// and commands can only be applied with exclusive access to the world.
true
}
fn has_deferred(&self) -> bool {
// This system itself doesn't have any commands to apply, but when it
// is pulled from the schedule to be ran, the executor will apply
// deferred commands from other systems.
false
fn flags(&self) -> SystemStateFlags {
// non-send , exclusive , no deferred
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
}
unsafe fn run_unsafe(
@ -203,6 +182,10 @@ impl System for ApplyDeferred {
Ok(())
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {}
fn run(&mut self, _input: SystemIn<'_, Self>, _world: &mut World) -> Self::Out {
// This system does nothing on its own. The executor will apply deferred
// commands from other systems instead of running this system.

View File

@ -19,6 +19,8 @@ use crate::{
system::ScheduleSystem,
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
#[cfg(feature = "hotpatching")]
use crate::{event::Events, HotPatched};
use super::__rust_begin_short_backtrace;
@ -443,6 +445,14 @@ impl ExecutorState {
return;
}
#[cfg(feature = "hotpatching")]
let should_update_hotpatch = !context
.environment
.world_cell
.get_resource::<Events<HotPatched>>()
.map(Events::is_empty)
.unwrap_or(true);
// can't borrow since loop mutably borrows `self`
let mut ready_systems = core::mem::take(&mut self.ready_systems_copy);
@ -460,6 +470,11 @@ impl ExecutorState {
// Therefore, no other reference to this system exists and there is no aliasing.
let system = unsafe { &mut *context.environment.systems[system_index].get() };
#[cfg(feature = "hotpatching")]
if should_update_hotpatch {
system.refresh_hotpatch();
}
if !self.can_run(system_index, conditions) {
// NOTE: exclusive systems with ambiguities are susceptible to
// being significantly displaced here (compared to single-threaded order)

View File

@ -16,6 +16,8 @@ use crate::{
},
world::World,
};
#[cfg(feature = "hotpatching")]
use crate::{event::Events, HotPatched};
use super::__rust_begin_short_backtrace;
@ -60,6 +62,12 @@ impl SystemExecutor for SimpleExecutor {
self.completed_systems |= skipped_systems;
}
#[cfg(feature = "hotpatching")]
let should_update_hotpatch = !world
.get_resource::<Events<HotPatched>>()
.map(Events::is_empty)
.unwrap_or(true);
for system_index in 0..schedule.systems.len() {
#[cfg(feature = "trace")]
let name = schedule.systems[system_index].name();
@ -120,6 +128,11 @@ impl SystemExecutor for SimpleExecutor {
#[cfg(feature = "trace")]
should_run_span.exit();
#[cfg(feature = "hotpatching")]
if should_update_hotpatch {
system.refresh_hotpatch();
}
// system has either been skipped or will run
self.completed_systems.insert(system_index);
@ -186,6 +199,12 @@ fn evaluate_and_fold_conditions(
world: &mut World,
error_handler: ErrorHandler,
) -> bool {
#[cfg(feature = "hotpatching")]
let should_update_hotpatch = !world
.get_resource::<Events<HotPatched>>()
.map(Events::is_empty)
.unwrap_or(true);
#[expect(
clippy::unnecessary_fold,
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
@ -208,6 +227,10 @@ fn evaluate_and_fold_conditions(
return false;
}
}
#[cfg(feature = "hotpatching")]
if should_update_hotpatch {
condition.refresh_hotpatch();
}
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
})
.fold(true, |acc, res| acc && res)

View File

@ -12,6 +12,8 @@ use crate::{
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
world::World,
};
#[cfg(feature = "hotpatching")]
use crate::{event::Events, HotPatched};
use super::__rust_begin_short_backtrace;
@ -60,6 +62,12 @@ impl SystemExecutor for SingleThreadedExecutor {
self.completed_systems |= skipped_systems;
}
#[cfg(feature = "hotpatching")]
let should_update_hotpatch = !world
.get_resource::<Events<HotPatched>>()
.map(Events::is_empty)
.unwrap_or(true);
for system_index in 0..schedule.systems.len() {
#[cfg(feature = "trace")]
let name = schedule.systems[system_index].name();
@ -121,6 +129,11 @@ impl SystemExecutor for SingleThreadedExecutor {
#[cfg(feature = "trace")]
should_run_span.exit();
#[cfg(feature = "hotpatching")]
if should_update_hotpatch {
system.refresh_hotpatch();
}
// system has either been skipped or will run
self.completed_systems.insert(system_index);
@ -204,6 +217,12 @@ fn evaluate_and_fold_conditions(
world: &mut World,
error_handler: ErrorHandler,
) -> bool {
#[cfg(feature = "hotpatching")]
let should_update_hotpatch = !world
.get_resource::<Events<HotPatched>>()
.map(Events::is_empty)
.unwrap_or(true);
#[expect(
clippy::unnecessary_fold,
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
@ -226,6 +245,10 @@ fn evaluate_and_fold_conditions(
return false;
}
}
#[cfg(feature = "hotpatching")]
if should_update_hotpatch {
condition.refresh_hotpatch();
}
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
})
.fold(true, |acc, res| acc && res)

View File

@ -29,6 +29,7 @@ mod tests {
use alloc::{string::ToString, vec, vec::Vec};
use core::sync::atomic::{AtomicU32, Ordering};
use crate::error::BevyError;
pub use crate::{
prelude::World,
resource::Resource,
@ -49,10 +50,10 @@ mod tests {
struct SystemOrder(Vec<u32>);
#[derive(Resource, Default)]
struct RunConditionBool(pub bool);
struct RunConditionBool(bool);
#[derive(Resource, Default)]
struct Counter(pub AtomicU32);
struct Counter(AtomicU32);
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
@ -252,12 +253,13 @@ mod tests {
}
mod conditions {
use crate::change_detection::DetectChanges;
use super::*;
#[test]
fn system_with_condition() {
fn system_with_condition_bool() {
let mut world = World::default();
let mut schedule = Schedule::default();
@ -276,6 +278,47 @@ mod tests {
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
}
#[test]
fn system_with_condition_result_unit() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems(
make_function_system(0).run_if(|| Err::<(), BevyError>(core::fmt::Error.into())),
);
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
schedule.add_systems(make_function_system(1).run_if(|| Ok(())));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![1]);
}
#[test]
fn system_with_condition_result_bool() {
let mut world = World::default();
let mut schedule = Schedule::default();
world.init_resource::<SystemOrder>();
schedule.add_systems((
make_function_system(0).run_if(|| Err::<bool, BevyError>(core::fmt::Error.into())),
make_function_system(1).run_if(|| Ok(false)),
));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
schedule.add_systems(make_function_system(2).run_if(|| Ok(true)));
schedule.run(&mut world);
assert_eq!(world.resource::<SystemOrder>().0, vec![2]);
}
#[test]
fn systems_with_distributive_condition() {
let mut world = World::default();
@ -874,7 +917,6 @@ mod tests {
}
#[test]
#[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"]
fn filtered_components() {
let mut world = World::new();
world.spawn(A);

View File

@ -166,7 +166,7 @@ impl Schedules {
writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
}
info!("{}", message);
info!("{message}");
}
/// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
@ -558,7 +558,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: Tick) {
pub fn check_change_ticks(&mut self, change_tick: Tick) {
for system in &mut self.executable.systems {
if !is_apply_deferred(system) {
system.check_change_tick(change_tick);
@ -1418,26 +1418,24 @@ impl ScheduleGraph {
if system_a.is_exclusive() || system_b.is_exclusive() {
conflicting_systems.push((a, b, Vec::new()));
} else {
let access_a = system_a.component_access();
let access_b = system_b.component_access();
if !access_a.is_compatible(access_b) {
match access_a.get_conflicts(access_b) {
AccessConflicts::Individual(conflicts) => {
let conflicts: Vec<_> = conflicts
.ones()
.map(ComponentId::get_sparse_set_index)
.filter(|id| !ignored_ambiguities.contains(id))
.collect();
if !conflicts.is_empty() {
conflicting_systems.push((a, b, conflicts));
}
}
AccessConflicts::All => {
// there is no specific component conflicting, but the systems are overall incompatible
// for example 2 systems with `Query<EntityMut>`
conflicting_systems.push((a, b, Vec::new()));
let access_a = system_a.component_access_set();
let access_b = system_b.component_access_set();
match access_a.get_conflicts(access_b) {
AccessConflicts::Individual(conflicts) => {
let conflicts: Vec<_> = conflicts
.ones()
.map(ComponentId::get_sparse_set_index)
.filter(|id| !ignored_ambiguities.contains(id))
.collect();
if !conflicts.is_empty() {
conflicting_systems.push((a, b, conflicts));
}
}
AccessConflicts::All => {
// there is no specific component conflicting, but the systems are overall incompatible
// for example 2 systems with `Query<EntityMut>`
conflicting_systems.push((a, b, Vec::new()));
}
}
}
}
@ -1707,10 +1705,7 @@ impl ScheduleGraph {
match self.settings.hierarchy_detection {
LogLevel::Ignore => unreachable!(),
LogLevel::Warn => {
error!(
"Schedule {schedule_label:?} has redundant edges:\n {}",
message
);
error!("Schedule {schedule_label:?} has redundant edges:\n {message}");
Ok(())
}
LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
@ -1912,7 +1907,7 @@ impl ScheduleGraph {
match self.settings.ambiguity_detection {
LogLevel::Ignore => Ok(()),
LogLevel::Warn => {
warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
warn!("Schedule {schedule_label:?} has ambiguities.\n{message}");
Ok(())
}
LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),

View File

@ -60,7 +60,93 @@ define_label!(
);
define_label!(
/// Types that identify logical groups of systems.
/// System sets are tag-like labels that can be used to group systems together.
///
/// This allows you to share configuration (like run conditions) across multiple systems,
/// and order systems or system sets relative to conceptual groups of systems.
/// To control the behavior of a system set as a whole, use [`Schedule::configure_sets`](crate::prelude::Schedule::configure_sets),
/// or the method of the same name on `App`.
///
/// Systems can belong to any number of system sets, reflecting multiple roles or facets that they might have.
/// For example, you may want to annotate a system as "consumes input" and "applies forces",
/// and ensure that your systems are ordered correctly for both of those sets.
///
/// System sets can belong to any number of other system sets,
/// allowing you to create nested hierarchies of system sets to group systems together.
/// Configuration applied to system sets will flow down to their members (including other system sets),
/// allowing you to set and modify the configuration in a single place.
///
/// Systems sets are also useful for exposing a consistent public API for dependencies
/// to hook into across versions of your crate,
/// allowing them to add systems to a specific set, or order relative to that set,
/// without leaking implementation details of the exact systems involved.
///
/// ## Defining new system sets
///
/// To create a new system set, use the `#[derive(SystemSet)]` macro.
/// Unit structs are a good choice for one-off sets.
///
/// ```rust
/// # use bevy_ecs::prelude::*;
///
/// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
/// struct PhysicsSystems;
/// ```
///
/// When you want to define several related system sets,
/// consider creating an enum system set.
/// Each variant will be treated as a separate system set.
///
/// ```rust
/// # use bevy_ecs::prelude::*;
///
/// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
/// enum CombatSystems {
/// TargetSelection,
/// DamageCalculation,
/// Cleanup,
/// }
/// ```
///
/// By convention, the listed order of the system set in the enum
/// corresponds to the order in which the systems are run.
/// Ordering must be explicitly added to ensure that this is the case,
/// but following this convention will help avoid confusion.
///
/// ### Adding systems to system sets
///
/// To add systems to a system set, call [`in_set`](crate::prelude::IntoScheduleConfigs::in_set) on the system function
/// while adding it to your app or schedule.
///
/// Like usual, these methods can be chained with other configuration methods like [`before`](crate::prelude::IntoScheduleConfigs::before),
/// or repeated to add systems to multiple sets.
///
/// ```rust
/// use bevy_ecs::prelude::*;
///
/// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
/// enum CombatSystems {
/// TargetSelection,
/// DamageCalculation,
/// Cleanup,
/// }
///
/// fn target_selection() {}
///
/// fn enemy_damage_calculation() {}
///
/// fn player_damage_calculation() {}
///
/// let mut schedule = Schedule::default();
/// // Configuring the sets to run in order.
/// schedule.configure_sets((CombatSystems::TargetSelection, CombatSystems::DamageCalculation, CombatSystems::Cleanup).chain());
///
/// // Adding a single system to a set.
/// schedule.add_systems(target_selection.in_set(CombatSystems::TargetSelection));
///
/// // Adding multiple systems to a set.
/// schedule.add_systems((player_damage_calculation, enemy_damage_calculation).in_set(CombatSystems::DamageCalculation));
/// ```
#[diagnostic::on_unimplemented(
note = "consider annotating `{Self}` with `#[derive(SystemSet)]`"
)]

View File

@ -475,9 +475,8 @@ impl Stepping {
Some(state) => state.clear_behaviors(),
None => {
warn!(
"stepping is not enabled for schedule {:?}; \
use `.add_stepping({:?})` to enable stepping",
label, label
"stepping is not enabled for schedule {label:?}; \
use `.add_stepping({label:?})` to enable stepping"
);
}
},
@ -486,9 +485,8 @@ impl Stepping {
Some(state) => state.set_behavior(system, behavior),
None => {
warn!(
"stepping is not enabled for schedule {:?}; \
use `.add_stepping({:?})` to enable stepping",
label, label
"stepping is not enabled for schedule {label:?}; \
use `.add_stepping({label:?})` to enable stepping"
);
}
}
@ -498,9 +496,8 @@ impl Stepping {
Some(state) => state.clear_behavior(system),
None => {
warn!(
"stepping is not enabled for schedule {:?}; \
use `.add_stepping({:?})` to enable stepping",
label, label
"stepping is not enabled for schedule {label:?}; \
use `.add_stepping({label:?})` to enable stepping"
);
}
}

View File

@ -127,26 +127,15 @@ where
self.name.clone()
}
fn component_access(&self) -> &crate::query::Access<crate::component::ComponentId> {
self.system.component_access()
}
fn component_access_set(
&self,
) -> &crate::query::FilteredAccessSet<crate::component::ComponentId> {
self.system.component_access_set()
}
fn is_send(&self) -> bool {
self.system.is_send()
}
fn is_exclusive(&self) -> bool {
self.system.is_exclusive()
}
fn has_deferred(&self) -> bool {
self.system.has_deferred()
#[inline]
fn flags(&self) -> super::SystemStateFlags {
self.system.flags()
}
#[inline]
@ -161,6 +150,12 @@ where
})
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.system.refresh_hotpatch();
}
#[inline]
fn apply_deferred(&mut self, world: &mut crate::prelude::World) {
self.system.apply_deferred(world);

View File

@ -604,7 +604,7 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)>
if !conflicts.is_empty() {
let accesses = conflicts.format_conflict_list(world);
let system_name = &meta.name;
panic!("error[B0002]: FilteredResources in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002");
panic!("error[B0002]: FilteredResources in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002");
}
if access.has_read_all_resources() {
@ -663,7 +663,7 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
if !conflicts.is_empty() {
let accesses = conflicts.format_conflict_list(world);
let system_name = &meta.name;
panic!("error[B0002]: FilteredResourcesMut in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002");
panic!("error[B0002]: FilteredResourcesMut in system {system_name} accesses resources(s){accesses} in a way that conflicts with a previous system parameter. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002");
}
if access.has_read_all_resources() {

View File

@ -4,7 +4,7 @@ use core::marker::PhantomData;
use crate::{
component::{ComponentId, Tick},
prelude::World,
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
schedule::InternedSystemSet,
system::{input::SystemInput, SystemIn, SystemParamValidationError},
world::unsafe_world_cell::UnsafeWorldCell,
@ -144,24 +144,13 @@ where
self.name.clone()
}
fn component_access(&self) -> &Access<ComponentId> {
self.component_access_set.combined_access()
}
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
&self.component_access_set
}
fn is_send(&self) -> bool {
self.a.is_send() && self.b.is_send()
}
fn is_exclusive(&self) -> bool {
self.a.is_exclusive() || self.b.is_exclusive()
}
fn has_deferred(&self) -> bool {
self.a.has_deferred() || self.b.has_deferred()
#[inline]
fn flags(&self) -> super::SystemStateFlags {
self.a.flags() | self.b.flags()
}
unsafe fn run_unsafe(
@ -182,6 +171,13 @@ where
)
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.a.refresh_hotpatch();
self.b.refresh_hotpatch();
}
#[inline]
fn apply_deferred(&mut self, world: &mut World) {
self.a.apply_deferred(world);
@ -363,24 +359,13 @@ where
self.name.clone()
}
fn component_access(&self) -> &Access<ComponentId> {
self.component_access_set.combined_access()
}
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
&self.component_access_set
}
fn is_send(&self) -> bool {
self.a.is_send() && self.b.is_send()
}
fn is_exclusive(&self) -> bool {
self.a.is_exclusive() || self.b.is_exclusive()
}
fn has_deferred(&self) -> bool {
self.a.has_deferred() || self.b.has_deferred()
#[inline]
fn flags(&self) -> super::SystemStateFlags {
self.a.flags() | self.b.flags()
}
unsafe fn run_unsafe(
@ -392,6 +377,13 @@ where
self.b.run_unsafe(value, world)
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.a.refresh_hotpatch();
self.b.refresh_hotpatch();
}
fn apply_deferred(&mut self, world: &mut World) {
self.a.apply_deferred(world);
self.b.apply_deferred(world);

View File

@ -1,6 +1,6 @@
use crate::{
component::{ComponentId, Tick},
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
schedule::{InternedSystemSet, SystemSet},
system::{
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem,
@ -13,7 +13,7 @@ use alloc::{borrow::Cow, vec, vec::Vec};
use core::marker::PhantomData;
use variadics_please::all_tuples;
use super::SystemParamValidationError;
use super::{SystemParamValidationError, SystemStateFlags};
/// A function system that runs with exclusive [`World`] access.
///
@ -26,6 +26,8 @@ where
F: ExclusiveSystemParamFunction<Marker>,
{
func: F,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFnPtr,
param_state: Option<<F::Param as ExclusiveSystemParam>::State>,
system_meta: SystemMeta,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
@ -58,6 +60,11 @@ where
fn into_system(func: Self) -> Self::System {
ExclusiveFunctionSystem {
func,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(
<F as ExclusiveSystemParamFunction<Marker>>::run,
)
.ptr_address(),
param_state: None,
system_meta: SystemMeta::new::<F>(),
marker: PhantomData,
@ -80,33 +87,18 @@ where
self.system_meta.name.clone()
}
#[inline]
fn component_access(&self) -> &Access<ComponentId> {
self.system_meta.component_access_set.combined_access()
}
#[inline]
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
&self.system_meta.component_access_set
}
#[inline]
fn is_send(&self) -> bool {
// exclusive systems should have access to non-send resources
fn flags(&self) -> SystemStateFlags {
// non-send , exclusive , no deferred
// the executor runs exclusive systems on the main thread, so this
// field reflects that constraint
false
}
#[inline]
fn is_exclusive(&self) -> bool {
true
}
#[inline]
fn has_deferred(&self) -> bool {
// exclusive systems have no deferred system params
false
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
}
#[inline]
@ -125,6 +117,20 @@ where
self.param_state.as_mut().expect(PARAM_MESSAGE),
&self.system_meta,
);
#[cfg(feature = "hotpatching")]
let out = {
let mut hot_fn =
subsecond::HotFn::current(<F as ExclusiveSystemParamFunction<Marker>>::run);
// SAFETY:
// - pointer used to call is from the current jump table
unsafe {
hot_fn
.try_call_with_ptr(self.current_ptr, (&mut self.func, world, input, params))
.expect("Error calling hotpatched system. Run a full rebuild")
}
};
#[cfg(not(feature = "hotpatching"))]
let out = self.func.run(world, input, params);
world.flush();
@ -134,6 +140,17 @@ where
})
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
let new = subsecond::HotFn::current(<F as ExclusiveSystemParamFunction<Marker>>::run)
.ptr_address();
if new != self.current_ptr {
log::debug!("system {} hotpatched", self.name());
}
self.current_ptr = new;
}
#[inline]
fn apply_deferred(&mut self, _world: &mut World) {
// "pure" exclusive systems do not have any buffers to apply.

View File

@ -1,7 +1,7 @@
use crate::{
component::{ComponentId, Tick},
prelude::FromWorld,
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
schedule::{InternedSystemSet, SystemSet},
system::{
check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam,
@ -17,7 +17,9 @@ use variadics_please::all_tuples;
#[cfg(feature = "trace")]
use tracing::{info_span, Span};
use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError};
use super::{
IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError, SystemStateFlags,
};
/// The metadata of a [`System`].
#[derive(Clone)]
@ -29,8 +31,7 @@ pub struct SystemMeta {
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
// SystemParams from overriding each other
is_send: bool,
has_deferred: bool,
flags: SystemStateFlags,
pub(crate) last_run: Tick,
#[cfg(feature = "trace")]
pub(crate) system_span: Span,
@ -44,8 +45,7 @@ impl SystemMeta {
Self {
name: name.into(),
component_access_set: FilteredAccessSet::default(),
is_send: true,
has_deferred: false,
flags: SystemStateFlags::empty(),
last_run: Tick::new(0),
#[cfg(feature = "trace")]
system_span: info_span!("system", name = name),
@ -78,7 +78,7 @@ impl SystemMeta {
/// Returns true if the system is [`Send`].
#[inline]
pub fn is_send(&self) -> bool {
self.is_send
!self.flags.intersects(SystemStateFlags::NON_SEND)
}
/// Sets the system to be not [`Send`].
@ -86,20 +86,20 @@ impl SystemMeta {
/// This is irreversible.
#[inline]
pub fn set_non_send(&mut self) {
self.is_send = false;
self.flags |= SystemStateFlags::NON_SEND;
}
/// Returns true if the system has deferred [`SystemParam`]'s
#[inline]
pub fn has_deferred(&self) -> bool {
self.has_deferred
self.flags.intersects(SystemStateFlags::DEFERRED)
}
/// Marks the system as having deferred buffers like [`Commands`](`super::Commands`)
/// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically.
#[inline]
pub fn set_has_deferred(&mut self) {
self.has_deferred = true;
self.flags |= SystemStateFlags::DEFERRED;
}
/// Returns a reference to the [`FilteredAccessSet`] for [`ComponentId`].
@ -306,6 +306,9 @@ impl<Param: SystemParam> SystemState<Param> {
) -> FunctionSystem<Marker, F> {
FunctionSystem {
func,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
.ptr_address(),
state: Some(FunctionSystemState {
param: self.param_state,
world_id: self.world_id,
@ -519,6 +522,8 @@ where
F: SystemParamFunction<Marker>,
{
func: F,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFnPtr,
state: Option<FunctionSystemState<F::Param>>,
system_meta: SystemMeta,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
@ -558,6 +563,9 @@ where
fn clone(&self) -> Self {
Self {
func: self.func.clone(),
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
.ptr_address(),
state: None,
system_meta: SystemMeta::new::<F>(),
marker: PhantomData,
@ -578,6 +586,9 @@ where
fn into_system(func: Self) -> Self::System {
FunctionSystem {
func,
#[cfg(feature = "hotpatching")]
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
.ptr_address(),
state: None,
system_meta: SystemMeta::new::<F>(),
marker: PhantomData,
@ -609,29 +620,14 @@ where
self.system_meta.name.clone()
}
#[inline]
fn component_access(&self) -> &Access<ComponentId> {
self.system_meta.component_access_set.combined_access()
}
#[inline]
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
&self.system_meta.component_access_set
}
#[inline]
fn is_send(&self) -> bool {
self.system_meta.is_send
}
#[inline]
fn is_exclusive(&self) -> bool {
false
}
#[inline]
fn has_deferred(&self) -> bool {
self.system_meta.has_deferred
fn flags(&self) -> SystemStateFlags {
self.system_meta.flags
}
#[inline]
@ -653,11 +649,35 @@ where
// will ensure that there are no data access conflicts.
let params =
unsafe { F::Param::get_param(&mut state.param, &self.system_meta, world, change_tick) };
#[cfg(feature = "hotpatching")]
let out = {
let mut hot_fn = subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run);
// SAFETY:
// - pointer used to call is from the current jump table
unsafe {
hot_fn
.try_call_with_ptr(self.current_ptr, (&mut self.func, input, params))
.expect("Error calling hotpatched system. Run a full rebuild")
}
};
#[cfg(not(feature = "hotpatching"))]
let out = self.func.run(input, params);
self.system_meta.last_run = change_tick;
out
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
let new = subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run).ptr_address();
if new != self.current_ptr {
log::debug!("system {} hotpatched", self.name());
}
self.current_ptr = new;
}
#[inline]
fn apply_deferred(&mut self, world: &mut World) {
let param_state = &mut self.state.as_mut().expect(Self::ERROR_UNINITIALIZED).param;

View File

@ -97,7 +97,7 @@
//! - [`EventWriter`](crate::event::EventWriter)
//! - [`NonSend`] and `Option<NonSend>`
//! - [`NonSendMut`] and `Option<NonSendMut>`
//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents)
//! - [`RemovedComponents`](crate::lifecycle::RemovedComponents)
//! - [`SystemName`]
//! - [`SystemChangeTick`]
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
@ -408,10 +408,10 @@ mod tests {
component::{Component, Components},
entity::{Entities, Entity},
error::Result,
lifecycle::RemovedComponents,
name::Name,
prelude::{AnyOf, EntityRef, Trigger},
prelude::{AnyOf, EntityRef, OnAdd, Trigger},
query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without},
removal_detection::RemovedComponents,
resource::Resource,
schedule::{
common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule,
@ -421,7 +421,7 @@ mod tests {
Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res,
ResMut, Single, StaticSystemParam, System, SystemState,
},
world::{DeferredWorld, EntityMut, FromWorld, OnAdd, World},
world::{DeferredWorld, EntityMut, FromWorld, World},
};
use super::ScheduleSystem;
@ -1166,7 +1166,9 @@ mod tests {
x.initialize(&mut world);
y.initialize(&mut world);
let conflicts = x.component_access().get_conflicts(y.component_access());
let conflicts = x
.component_access_set()
.get_conflicts(y.component_access_set());
let b_id = world
.components()
.get_resource_id(TypeId::of::<B>())
@ -1630,7 +1632,7 @@ mod tests {
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_world_and_entity_mut_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001"
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_world_and_entity_mut_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
fn assert_world_and_entity_mut_system_does_conflict_first() {
fn system(_query: &World, _q2: Query<EntityMut>) {}
@ -1648,7 +1650,7 @@ mod tests {
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_ref_and_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001"
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_ref_and_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
fn assert_entity_ref_and_entity_mut_system_does_conflict() {
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
@ -1657,7 +1659,7 @@ mod tests {
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001"
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
fn assert_entity_mut_system_does_conflict() {
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
@ -1666,7 +1668,7 @@ mod tests {
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityRef, ()> in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001"
expected = "error[B0001]: Query<EntityRef, ()> in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
fn assert_deferred_world_and_entity_ref_system_does_conflict_first() {
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}

View File

@ -6,7 +6,7 @@ use crate::{
error::Result,
never::Never,
prelude::{Bundle, Trigger},
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
schedule::{Fallible, Infallible},
system::{input::SystemIn, System},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
@ -116,29 +116,14 @@ where
self.observer.name()
}
#[inline]
fn component_access(&self) -> &Access<ComponentId> {
self.observer.component_access()
}
#[inline]
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
self.observer.component_access_set()
}
#[inline]
fn is_send(&self) -> bool {
self.observer.is_send()
}
#[inline]
fn is_exclusive(&self) -> bool {
self.observer.is_exclusive()
}
#[inline]
fn has_deferred(&self) -> bool {
self.observer.has_deferred()
fn flags(&self) -> super::SystemStateFlags {
self.observer.flags()
}
#[inline]
@ -151,6 +136,12 @@ where
Ok(())
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.observer.refresh_hotpatch();
}
#[inline]
fn apply_deferred(&mut self, world: &mut World) {
self.observer.apply_deferred(world);

View File

@ -2012,17 +2012,67 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
self.as_nop().get(entity).is_ok()
}
/// Returns a [`QueryLens`] that can be used to get a query with a more general fetch.
/// Returns a [`QueryLens`] that can be used to construct a new [`Query`] giving more
/// restrictive access to the entities matched by the current query.
///
/// For example, this can transform a `Query<(&A, &mut B)>` to a `Query<&B>`.
/// This can be useful for passing the query to another function. Note that since
/// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added),
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be
/// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`]
/// A transmute is valid only if `NewD` has a subset of the read, write, and required access
/// of the current query. A precise description of the access required by each parameter
/// type is given in the table below, but typical uses are to:
/// * Remove components, e.g. `Query<(&A, &B)>` to `Query<&A>`.
/// * Retrieve an existing component with reduced or equal access, e.g. `Query<&mut A>` to `Query<&A>`
/// or `Query<&T>` to `Query<Ref<T>>`.
/// * Add parameters with no new access, for example adding an `Entity` parameter.
///
/// Note that since filter terms are dropped, non-archetypal filters like
/// [`Added`], [`Changed`] and [`Spawned`] will not be respected. To maintain or change filter
/// terms see [`Self::transmute_lens_filtered`].
///
/// |`QueryData` parameter type|Access required|
/// |----|----|
/// |[`Entity`], [`EntityLocation`], [`SpawnDetails`], [`&Archetype`], [`Has<T>`], [`PhantomData<T>`]|No access|
/// |[`EntityMut`]|Read and write access to all components, but no required access|
/// |[`EntityRef`]|Read access to all components, but no required access|
/// |`&T`, [`Ref<T>`]|Read and required access to `T`|
/// |`&mut T`, [`Mut<T>`]|Read, write and required access to `T`|
/// |[`Option<T>`], [`AnyOf<(D, ...)>`]|Read and write access to `T`, but no required access|
/// |Tuples of query data and<br/>`#[derive(QueryData)]` structs|The union of the access of their subqueries|
/// |[`FilteredEntityRef`], [`FilteredEntityMut`]|Determined by the [`QueryBuilder`] used to construct them. Any query can be transmuted to them, and they will receive the access of the source query. When combined with other `QueryData`, they will receive any access of the source query that does not conflict with the other data|
///
/// `transmute_lens` drops filter terms, but [`Self::transmute_lens_filtered`] supports returning a [`QueryLens`] with a new
/// filter type - the access required by filter parameters are as follows.
///
/// |`QueryFilter` parameter type|Access required|
/// |----|----|
/// |[`Added<T>`], [`Changed<T>`]|Read and required access to `T`|
/// |[`With<T>`], [`Without<T>`]|No access|
/// |[`Or<(T, ...)>`]|Read access of the subqueries, but no required access|
/// |Tuples of query filters and `#[derive(QueryFilter)]` structs|The union of the access of their subqueries|
///
/// [`Added`]: crate::query::Added
/// [`Added<T>`]: crate::query::Added
/// [`AnyOf<(D, ...)>`]: crate::query::AnyOf
/// [`&Archetype`]: crate::archetype::Archetype
/// [`Changed`]: crate::query::Changed
/// [`Changed<T>`]: crate::query::Changed
/// [`EntityMut`]: crate::world::EntityMut
/// [`EntityLocation`]: crate::entity::EntityLocation
/// [`EntityRef`]: crate::world::EntityRef
/// [`FilteredEntityRef`]: crate::world::FilteredEntityRef
/// [`FilteredEntityMut`]: crate::world::FilteredEntityMut
/// [`Has<T>`]: crate::query::Has
/// [`Mut<T>`]: crate::world::Mut
/// [`Or<(T, ...)>`]: crate::query::Or
/// [`QueryBuilder`]: crate::query::QueryBuilder
/// [`Ref<T>`]: crate::world::Ref
/// [`SpawnDetails`]: crate::query::SpawnDetails
/// [`Spawned`]: crate::query::Spawned
/// [`With<T>`]: crate::query::With
/// [`Without<T>`]: crate::query::Without
///
/// ## Panics
///
/// This will panic if `NewD` is not a subset of the original fetch `D`
/// This will panic if the access required by `NewD` is not a subset of that required by
/// the original fetch `D`.
///
/// ## Example
///
@ -2061,30 +2111,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// # schedule.run(&mut world);
/// ```
///
/// ## Allowed Transmutes
///
/// Besides removing parameters from the query,
/// you can also make limited changes to the types of parameters.
/// The new query must have a subset of the *read*, *write*, and *required* access of the original query.
///
/// * `&mut T` and [`Mut<T>`](crate::change_detection::Mut) have read, write, and required access to `T`
/// * `&T` and [`Ref<T>`](crate::change_detection::Ref) have read and required access to `T`
/// * [`Option<D>`] and [`AnyOf<(D, ...)>`](crate::query::AnyOf) have the read and write access of the subqueries, but no required access
/// * Tuples of query data and `#[derive(QueryData)]` structs have the union of the access of their subqueries
/// * [`EntityMut`](crate::world::EntityMut) has read and write access to all components, but no required access
/// * [`EntityRef`](crate::world::EntityRef) has read access to all components, but no required access
/// * [`Entity`], [`EntityLocation`], [`SpawnDetails`], [`&Archetype`], [`Has<T>`], and [`PhantomData<T>`] have no access at all,
/// so can be added to any query
/// * [`FilteredEntityRef`](crate::world::FilteredEntityRef) and [`FilteredEntityMut`](crate::world::FilteredEntityMut)
/// have access determined by the [`QueryBuilder`](crate::query::QueryBuilder) used to construct them.
/// Any query can be transmuted to them, and they will receive the access of the source query.
/// When combined with other `QueryData`, they will receive any access of the source query that does not conflict with the other data.
/// * [`Added<T>`](crate::query::Added) and [`Changed<T>`](crate::query::Changed) filters have read and required access to `T`
/// * [`With<T>`](crate::query::With) and [`Without<T>`](crate::query::Without) filters have no access at all,
/// so can be added to any query
/// * Tuples of query filters and `#[derive(QueryFilter)]` structs have the union of the access of their subqueries
/// * [`Or<(F, ...)>`](crate::query::Or) filters have the read access of the subqueries, but no required access
///
/// ### Examples of valid transmutes
///
/// ```rust
@ -2161,28 +2187,21 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// // Nested inside of an `Or` filter, they have the same access as `Option<&T>`.
/// assert_valid_transmute_filtered::<Option<&T>, (), Entity, Or<(Changed<T>, With<U>)>>();
/// ```
///
/// [`EntityLocation`]: crate::entity::EntityLocation
/// [`SpawnDetails`]: crate::query::SpawnDetails
/// [`&Archetype`]: crate::archetype::Archetype
/// [`Has<T>`]: crate::query::Has
#[track_caller]
pub fn transmute_lens<NewD: QueryData>(&mut self) -> QueryLens<'_, NewD> {
self.transmute_lens_filtered::<NewD, ()>()
}
/// Returns a [`QueryLens`] that can be used to get a query with a more general fetch.
/// Returns a [`QueryLens`] that can be used to construct a new `Query` giving more restrictive
/// access to the entities matched by the current query.
///
/// This consumes the [`Query`] to return results with the actual "inner" world lifetime.
///
/// For example, this can transform a `Query<(&A, &mut B)>` to a `Query<&B>`.
/// This can be useful for passing the query to another function. Note that since
/// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added),
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be
/// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`]
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
///
/// ## Panics
///
/// This will panic if `NewD` is not a subset of the original fetch `Q`
/// This will panic if `NewD` is not a subset of the original fetch `D`
///
/// ## Example
///
@ -2221,22 +2240,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// # schedule.run(&mut world);
/// ```
///
/// ## Allowed Transmutes
///
/// Besides removing parameters from the query, you can also
/// make limited changes to the types of parameters.
///
/// * Can always add/remove [`Entity`]
/// * Can always add/remove [`EntityLocation`]
/// * Can always add/remove [`&Archetype`]
/// * `Ref<T>` <-> `&T`
/// * `&mut T` -> `&T`
/// * `&mut T` -> `Ref<T>`
/// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef)
///
/// [`EntityLocation`]: crate::entity::EntityLocation
/// [`&Archetype`]: crate::archetype::Archetype
///
/// # See also
///
/// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`].
@ -2247,6 +2250,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Equivalent to [`Self::transmute_lens`] but also includes a [`QueryFilter`] type.
///
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
///
/// Note that the lens will iterate the same tables and archetypes as the original query. This means that
/// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without)
/// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added),
@ -2262,10 +2267,13 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Equivalent to [`Self::transmute_lens_inner`] but also includes a [`QueryFilter`] type.
/// This consumes the [`Query`] to return results with the actual "inner" world lifetime.
///
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
///
/// Note that the lens will iterate the same tables and archetypes as the original query. This means that
/// additional archetypal query terms like [`With`](crate::query::With) and [`Without`](crate::query::Without)
/// will not necessarily be respected and non-archetypal terms like [`Added`](crate::query::Added),
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they
/// are in the type signature.
///
/// # See also
///
@ -2623,6 +2631,36 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Populated<'w, 's, D, F> {
}
}
impl<'w, 's, D: QueryData, F: QueryFilter> IntoIterator for Populated<'w, 's, D, F> {
type Item = <Query<'w, 's, D, F> as IntoIterator>::Item;
type IntoIter = <Query<'w, 's, D, F> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, 'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'a Populated<'w, 's, D, F> {
type Item = <&'a Query<'w, 's, D, F> as IntoIterator>::Item;
type IntoIter = <&'a Query<'w, 's, D, F> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.deref().into_iter()
}
}
impl<'a, 'w, 's, D: QueryData, F: QueryFilter> IntoIterator for &'a mut Populated<'w, 's, D, F> {
type Item = <&'a mut Query<'w, 's, D, F> as IntoIterator>::Item;
type IntoIter = <&'a mut Query<'w, 's, D, F> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.deref_mut().into_iter()
}
}
#[cfg(test)]
mod tests {
use crate::{prelude::*, query::QueryEntityError};

View File

@ -3,12 +3,12 @@ use alloc::{borrow::Cow, vec::Vec};
use crate::{
component::{ComponentId, Tick},
error::Result,
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
system::{input::SystemIn, BoxedSystem, System, SystemInput},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
};
use super::{IntoSystem, SystemParamValidationError};
use super::{IntoSystem, SystemParamValidationError, SystemStateFlags};
/// A wrapper system to change a system that returns `()` to return `Ok(())` to make it into a [`ScheduleSystem`]
pub struct InfallibleSystemWrapper<S: System<In = ()>>(S);
@ -33,29 +33,14 @@ impl<S: System<In = ()>> System for InfallibleSystemWrapper<S> {
self.0.type_id()
}
#[inline]
fn component_access(&self) -> &Access<ComponentId> {
self.0.component_access()
}
#[inline]
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
self.0.component_access_set()
}
#[inline]
fn is_send(&self) -> bool {
self.0.is_send()
}
#[inline]
fn is_exclusive(&self) -> bool {
self.0.is_exclusive()
}
#[inline]
fn has_deferred(&self) -> bool {
self.0.has_deferred()
fn flags(&self) -> SystemStateFlags {
self.0.flags()
}
#[inline]
@ -68,6 +53,12 @@ impl<S: System<In = ()>> System for InfallibleSystemWrapper<S> {
Ok(())
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.0.refresh_hotpatch();
}
#[inline]
fn apply_deferred(&mut self, world: &mut World) {
self.0.apply_deferred(world);
@ -158,24 +149,13 @@ where
self.system.name()
}
fn component_access(&self) -> &Access<ComponentId> {
self.system.component_access()
}
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
self.system.component_access_set()
}
fn is_send(&self) -> bool {
self.system.is_send()
}
fn is_exclusive(&self) -> bool {
self.system.is_exclusive()
}
fn has_deferred(&self) -> bool {
self.system.has_deferred()
#[inline]
fn flags(&self) -> SystemStateFlags {
self.system.flags()
}
unsafe fn run_unsafe(
@ -186,6 +166,12 @@ where
self.system.run_unsafe(&mut self.value, world)
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.system.refresh_hotpatch();
}
fn apply_deferred(&mut self, world: &mut World) {
self.system.apply_deferred(world);
}
@ -261,24 +247,13 @@ where
self.system.name()
}
fn component_access(&self) -> &Access<ComponentId> {
self.system.component_access()
}
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
self.system.component_access_set()
}
fn is_send(&self) -> bool {
self.system.is_send()
}
fn is_exclusive(&self) -> bool {
self.system.is_exclusive()
}
fn has_deferred(&self) -> bool {
self.system.has_deferred()
#[inline]
fn flags(&self) -> SystemStateFlags {
self.system.flags()
}
unsafe fn run_unsafe(
@ -293,6 +268,12 @@ where
self.system.run_unsafe(value, world)
}
#[cfg(feature = "hotpatching")]
#[inline]
fn refresh_hotpatch(&mut self) {
self.system.refresh_hotpatch();
}
fn apply_deferred(&mut self, world: &mut World) {
self.system.apply_deferred(world);
}

View File

@ -2,13 +2,14 @@
clippy::module_inception,
reason = "This instance of module inception is being discussed; see #17353."
)]
use bitflags::bitflags;
use core::fmt::Debug;
use log::warn;
use thiserror::Error;
use crate::{
component::{ComponentId, Tick},
query::{Access, FilteredAccessSet},
query::FilteredAccessSet,
schedule::InternedSystemSet,
system::{input::SystemInput, SystemIn},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
@ -19,6 +20,18 @@ use core::any::TypeId;
use super::{IntoSystem, SystemParamValidationError};
bitflags! {
/// Bitflags representing system states and requirements.
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct SystemStateFlags: u8 {
/// Set if system cannot be sent across threads
const NON_SEND = 1 << 0;
/// Set if system requires exclusive World access
const EXCLUSIVE = 1 << 1;
/// Set if system has deferred buffers.
const DEFERRED = 1 << 2;
}
}
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
///
/// Systems are functions with all arguments implementing
@ -44,20 +57,29 @@ pub trait System: Send + Sync + 'static {
TypeId::of::<Self>()
}
/// Returns the system's component [`Access`].
fn component_access(&self) -> &Access<ComponentId>;
/// Returns the system's component [`FilteredAccessSet`].
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId>;
/// Returns the [`SystemStateFlags`] of the system.
fn flags(&self) -> SystemStateFlags;
/// Returns true if the system is [`Send`].
fn is_send(&self) -> bool;
#[inline]
fn is_send(&self) -> bool {
!self.flags().intersects(SystemStateFlags::NON_SEND)
}
/// Returns true if the system must be run exclusively.
fn is_exclusive(&self) -> bool;
#[inline]
fn is_exclusive(&self) -> bool {
self.flags().intersects(SystemStateFlags::EXCLUSIVE)
}
/// Returns true if system has deferred buffers.
fn has_deferred(&self) -> bool;
#[inline]
fn has_deferred(&self) -> bool {
self.flags().intersects(SystemStateFlags::DEFERRED)
}
/// Runs the system with the given input in the world. Unlike [`System::run`], this function
/// can be called in parallel with other systems and may break Rust's aliasing rules
@ -76,6 +98,10 @@ pub trait System: Send + Sync + 'static {
unsafe fn run_unsafe(&mut self, input: SystemIn<'_, Self>, world: UnsafeWorldCell)
-> Self::Out;
/// Refresh the inner pointer based on the latest hot patch jump table
#[cfg(feature = "hotpatching")]
fn refresh_hotpatch(&mut self);
/// Runs the system with the given input in the world.
///
/// For [read-only](ReadOnlySystem) systems, see [`run_readonly`], which can be called using `&World`.
@ -452,7 +478,7 @@ mod tests {
let result = world.run_system_once(system);
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist";
let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.";
assert_eq!(expected, result.unwrap_err().to_string());
}
}

View File

@ -151,7 +151,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
/// let mut world = World::new();
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
/// assert!(err.to_string().ends_with(expected));
/// assert!(err.to_string().contains(expected));
/// ```
///
/// ## Builders
@ -380,7 +380,7 @@ fn assert_component_access_compatibility(
if !accesses.is_empty() {
accesses.push(' ');
}
panic!("error[B0001]: Query<{}, {}> in system {system_name} accesses component(s) {accesses}in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001", ShortName(query_type), ShortName(filter_type));
panic!("error[B0001]: Query<{}, {}> in system {system_name} accesses component(s) {accesses}in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001", ShortName(query_type), ShortName(filter_type));
}
// SAFETY: Relevant query ComponentId access is applied to SystemMeta. If
@ -728,7 +728,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> {
let combined_access = system_meta.component_access_set.combined_access();
assert!(
!combined_access.has_resource_write(component_id),
"error[B0002]: Res<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
"error[B0002]: Res<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
core::any::type_name::<T>(),
system_meta.name,
);
@ -801,11 +801,11 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> {
let combined_access = system_meta.component_access_set.combined_access();
if combined_access.has_resource_write(component_id) {
panic!(
"error[B0002]: ResMut<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
"error[B0002]: ResMut<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
core::any::type_name::<T>(), system_meta.name);
} else if combined_access.has_resource_read(component_id) {
panic!(
"error[B0002]: ResMut<{}> in system {} conflicts with a previous Res<{0}> access. Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
"error[B0002]: ResMut<{}> in system {} conflicts with a previous Res<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
core::any::type_name::<T>(), system_meta.name);
}
system_meta
@ -1357,7 +1357,7 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> {
let combined_access = system_meta.component_access_set.combined_access();
assert!(
!combined_access.has_resource_write(component_id),
"error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
"error[B0002]: NonSend<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
core::any::type_name::<T>(),
system_meta.name,
);
@ -1430,11 +1430,11 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> {
let combined_access = system_meta.component_access_set.combined_access();
if combined_access.has_component_write(component_id) {
panic!(
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous mutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
core::any::type_name::<T>(), system_meta.name);
} else if combined_access.has_component_read(component_id) {
panic!(
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access. See: https://bevyengine.org/learn/errors/b0002",
"error[B0002]: NonSendMut<{}> in system {} conflicts with a previous immutable resource access ({0}). Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002",
core::any::type_name::<T>(), system_meta.name);
}
system_meta
@ -2578,7 +2578,11 @@ impl Display for SystemParamValidationError {
ShortName(&self.param),
self.field,
self.message
)
)?;
if !self.skipped {
write!(fmt, "\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.")?;
}
Ok(())
}
}

View File

@ -880,7 +880,7 @@ mod tests {
result,
Err(RegisteredSystemError::InvalidParams { .. })
));
let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist");
let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.");
assert_eq!(expected, result.unwrap_err().to_string());
}

View File

@ -1,55 +0,0 @@
//! Internal components used by bevy with a fixed component id.
//! Constants are used to skip [`TypeId`] lookups in hot paths.
use super::*;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
/// [`ComponentId`] for [`OnAdd`]
pub const ON_ADD: ComponentId = ComponentId::new(0);
/// [`ComponentId`] for [`OnInsert`]
pub const ON_INSERT: ComponentId = ComponentId::new(1);
/// [`ComponentId`] for [`OnReplace`]
pub const ON_REPLACE: ComponentId = ComponentId::new(2);
/// [`ComponentId`] for [`OnRemove`]
pub const ON_REMOVE: ComponentId = ComponentId::new(3);
/// [`ComponentId`] for [`OnDespawn`]
pub const ON_DESPAWN: ComponentId = ComponentId::new(4);
/// Trigger emitted when a component is inserted onto an entity that does not already have that
/// component. Runs before `OnInsert`.
/// See [`crate::component::ComponentHooks::on_add`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnAdd;
/// Trigger emitted when a component is inserted, regardless of whether or not the entity already
/// had that component. Runs after `OnAdd`, if it ran.
/// See [`crate::component::ComponentHooks::on_insert`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnInsert;
/// Trigger emitted when a component is inserted onto an entity that already has that component.
/// Runs before the value is replaced, so you can still access the original component data.
/// See [`crate::component::ComponentHooks::on_replace`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnReplace;
/// Trigger emitted when a component is removed from an entity, and runs before the component is
/// removed, so you can still access the component data.
/// See [`crate::component::ComponentHooks::on_remove`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnRemove;
/// Trigger emitted for each component on an entity when it is despawned.
/// See [`crate::component::ComponentHooks::on_despawn`] for more information.
#[derive(Event, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
pub struct OnDespawn;

Some files were not shown because too many files have changed in this diff Show More