Merge branch 'main' into Remove-entity-reserving/pending/flushing-system
This commit is contained in:
commit
d899b389d6
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1 +1 @@
|
||||
custom: https://bevyengine.org/donate/
|
||||
custom: https://bevy.org/donate/
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/docs_improvement.md
vendored
2
.github/ISSUE_TEMPLATE/docs_improvement.md
vendored
@ -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.
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -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: |
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -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
|
||||
|
||||
|
||||
2
.github/workflows/welcome.yml
vendored
2
.github/workflows/welcome.yml
vendored
@ -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 ✨`
|
||||
})
|
||||
|
||||
@ -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!
|
||||
|
||||
65
Cargo.toml
65
Cargo.toml
@ -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
|
||||
|
||||
24
README.md
24
README.md
@ -1,4 +1,4 @@
|
||||
# [](https://bevyengine.org)
|
||||
# [](https://bevy.org)
|
||||
|
||||
[](https://github.com/bevyengine/bevy#license)
|
||||
[](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.
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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]
|
||||
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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],
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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};
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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},
|
||||
|
||||
42
crates/bevy_app/src/hotpatch.rs
Normal file
42
crates/bevy_app/src/hotpatch.rs
Normal 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();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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`.
|
||||
|
||||
551
crates/bevy_app/src/propagate.rs
Normal file
551
crates/bevy_app/src/propagate.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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]
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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,)+
|
||||
};
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
32
crates/bevy_core_widgets/Cargo.toml
Normal file
32
crates/bevy_core_widgets/Cargo.toml
Normal 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
|
||||
141
crates/bevy_core_widgets/src/core_button.rs
Normal file
141
crates/bevy_core_widgets/src/core_button.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
27
crates/bevy_core_widgets/src/lib.rs
Normal file
27
crates/bevy_core_widgets/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@ -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"]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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>,
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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>>,
|
||||
|
||||
@ -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.
|
||||
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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) {}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()));
|
||||
|
||||
@ -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>>()
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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::{
|
||||
|
||||
606
crates/bevy_ecs/src/lifecycle.rs
Normal file
606
crates/bevy_ecs/src/lifecycle.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
///
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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.
|
||||
///
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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},
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)),
|
||||
|
||||
@ -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)]`"
|
||||
)]
|
||||
|
||||
@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>) {}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user