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?
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Check for typos
|
- name: Check for typos
|
||||||
uses: crate-ci/typos@v1.32.0
|
uses: crate-ci/typos@v1.33.1
|
||||||
- name: Typos info
|
- name: Typos info
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -82,7 +82,7 @@ jobs:
|
|||||||
- name: Finalize documentation
|
- name: Finalize documentation
|
||||||
run: |
|
run: |
|
||||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=bevy/index.html\">" > target/doc/index.html
|
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
|
echo $'User-Agent: *\nDisallow: /' > target/doc/robots.txt
|
||||||
rm target/doc/.lock
|
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,
|
repo: context.repo.repo,
|
||||||
body: `**Welcome**, new contributor!
|
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
|
# Contributing to Bevy
|
||||||
|
|
||||||
If you'd like to help build Bevy, start by reading this
|
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"]
|
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||||
description = "A refreshingly simple data-driven game engine and app framework"
|
description = "A refreshingly simple data-driven game engine and app framework"
|
||||||
exclude = ["assets/", "tools/", ".github/", "crates/", "examples/wasm/assets/"]
|
exclude = ["assets/", "tools/", ".github/", "crates/", "examples/wasm/assets/"]
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
@ -72,6 +72,7 @@ allow_attributes_without_reason = "warn"
|
|||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
missing_docs = "warn"
|
missing_docs = "warn"
|
||||||
|
mismatched_lifetime_syntaxes = "allow"
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
||||||
unsafe_code = "deny"
|
unsafe_code = "deny"
|
||||||
unsafe_op_in_unsafe_fn = "warn"
|
unsafe_op_in_unsafe_fn = "warn"
|
||||||
@ -133,6 +134,7 @@ default = [
|
|||||||
"bevy_audio",
|
"bevy_audio",
|
||||||
"bevy_color",
|
"bevy_color",
|
||||||
"bevy_core_pipeline",
|
"bevy_core_pipeline",
|
||||||
|
"bevy_core_widgets",
|
||||||
"bevy_anti_aliasing",
|
"bevy_anti_aliasing",
|
||||||
"bevy_gilrs",
|
"bevy_gilrs",
|
||||||
"bevy_gizmos",
|
"bevy_gizmos",
|
||||||
@ -291,6 +293,9 @@ bevy_log = ["bevy_internal/bevy_log"]
|
|||||||
# Enable input focus subsystem
|
# Enable input focus subsystem
|
||||||
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
|
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)
|
# 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"]
|
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.
|
# Enables use of browser APIs. Note this is currently only applicable on `wasm32` architectures.
|
||||||
web = ["bevy_internal/web"]
|
web = ["bevy_internal/web"]
|
||||||
|
|
||||||
|
# Enable hotpatching of Bevy systems
|
||||||
|
hotpatching = ["bevy_internal/hotpatching"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||||
tracing = { version = "0.1", default-features = false, optional = true }
|
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)"
|
category = "UI (User Interface)"
|
||||||
wasm = true
|
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]]
|
[[example]]
|
||||||
name = "viewport_debug"
|
name = "viewport_debug"
|
||||||
path = "examples/ui/viewport_debug.rs"
|
path = "examples/ui/viewport_debug.rs"
|
||||||
@ -3922,6 +3941,16 @@ description = "A simple 2D screen shake effect"
|
|||||||
category = "Camera"
|
category = "Camera"
|
||||||
wasm = true
|
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]
|
[package.metadata.example.fps_overlay]
|
||||||
name = "FPS overlay"
|
name = "FPS overlay"
|
||||||
@ -4401,3 +4430,37 @@ name = "Cooldown"
|
|||||||
description = "Example for cooldown on button clicks"
|
description = "Example for cooldown on button clicks"
|
||||||
category = "Usage"
|
category = "Usage"
|
||||||
wasm = true
|
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://github.com/bevyengine/bevy#license)
|
||||||
[](https://crates.io/crates/bevy)
|
[](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
|
## 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.
|
**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.
|
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
|
## About
|
||||||
|
|
||||||
* **[Features](https://bevyengine.org):** A quick overview of Bevy's features.
|
* **[Features](https://bevy.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.
|
* **[News](https://bevy.org/news/)**: A development blog that covers our progress, plans and shiny new features.
|
||||||
|
|
||||||
## Docs
|
## 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.
|
* **[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.
|
* **[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
|
## 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.
|
* **[Discord](https://discord.gg/bevy):** Bevy's official discord server.
|
||||||
* **[Reddit](https://reddit.com/r/bevy):** Bevy's official subreddit.
|
* **[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!
|
* **[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
|
### 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
|
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!
|
[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
|
## 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:
|
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
|
```sh
|
||||||
@ -84,7 +84,7 @@ fn main() {
|
|||||||
|
|
||||||
### Fast Compiles
|
### 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]
|
## [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.
|
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 next line need to stay exactly as is. It is required for BrowserStack sponsorship. -->
|
||||||
This project is tested with BrowserStack.
|
This project is tested with BrowserStack.
|
||||||
|
|||||||
@ -32,7 +32,7 @@ fn segment_ease(c: &mut Criterion) {
|
|||||||
|
|
||||||
fn curve_position(c: &mut Criterion) {
|
fn curve_position(c: &mut Criterion) {
|
||||||
/// A helper function that benchmarks calling [`CubicCurve::position()`] over a generic [`VectorSpace`].
|
/// 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>,
|
group: &mut BenchmarkGroup<M>,
|
||||||
name: &str,
|
name: &str,
|
||||||
curve: CubicCurve<P>,
|
curve: CubicCurve<P>,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_a11y"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides accessibility support for Bevy Engine"
|
description = "Provides accessibility support for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy", "accessibility", "a11y"]
|
keywords = ["bevy", "accessibility", "a11y"]
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_animation"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides animation functionality for Bevy Engine"
|
description = "Provides animation functionality for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -55,7 +55,7 @@ pub struct CubicKeyframeCurve<T> {
|
|||||||
|
|
||||||
impl<V> Curve<V> for CubicKeyframeCurve<V>
|
impl<V> Curve<V> for CubicKeyframeCurve<V>
|
||||||
where
|
where
|
||||||
V: VectorSpace,
|
V: VectorSpace<Scalar = f32>,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn domain(&self) -> Interval {
|
fn domain(&self) -> Interval {
|
||||||
@ -179,7 +179,7 @@ pub struct WideLinearKeyframeCurve<T> {
|
|||||||
|
|
||||||
impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>
|
impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>
|
||||||
where
|
where
|
||||||
T: VectorSpace,
|
T: VectorSpace<Scalar = f32>,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn domain(&self) -> Interval {
|
fn domain(&self) -> Interval {
|
||||||
@ -289,7 +289,7 @@ pub struct WideCubicKeyframeCurve<T> {
|
|||||||
|
|
||||||
impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>
|
impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>
|
||||||
where
|
where
|
||||||
T: VectorSpace,
|
T: VectorSpace<Scalar = f32>,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn domain(&self) -> Interval {
|
fn domain(&self) -> Interval {
|
||||||
@ -406,7 +406,7 @@ fn cubic_spline_interpolation<T>(
|
|||||||
step_duration: f32,
|
step_duration: f32,
|
||||||
) -> T
|
) -> T
|
||||||
where
|
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;
|
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)
|
value_start * (coeffs.x * lerp + 1.0)
|
||||||
@ -415,7 +415,7 @@ where
|
|||||||
+ tangent_in_end * step_duration * lerp * coeffs.w
|
+ 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,
|
width: usize,
|
||||||
first: &'a [T],
|
first: &'a [T],
|
||||||
second: &'a [T],
|
second: &'a [T],
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
//! Animation for the game engine Bevy
|
//! Animation for the game engine Bevy
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_anti_aliasing"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides various anti aliasing implementations for Bevy Engine"
|
description = "Provides various anti aliasing implementations for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
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)]
|
#![forbid(unsafe_code)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
use bevy_app::Plugin;
|
use bevy_app::Plugin;
|
||||||
use contrast_adaptive_sharpening::CasPlugin;
|
use contrast_adaptive_sharpening::CasPlugin;
|
||||||
use fxaa::FxaaPlugin;
|
use fxaa::FxaaPlugin;
|
||||||
use smaa::SmaaPlugin;
|
use smaa::SmaaPlugin;
|
||||||
|
use taa::TemporalAntiAliasPlugin;
|
||||||
|
|
||||||
pub mod contrast_adaptive_sharpening;
|
pub mod contrast_adaptive_sharpening;
|
||||||
pub mod experimental;
|
|
||||||
pub mod fxaa;
|
pub mod fxaa;
|
||||||
pub mod smaa;
|
pub mod smaa;
|
||||||
|
pub mod taa;
|
||||||
mod taa;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct AntiAliasingPlugin;
|
pub struct AntiAliasingPlugin;
|
||||||
impl Plugin for AntiAliasingPlugin {
|
impl Plugin for AntiAliasingPlugin {
|
||||||
fn build(&self, app: &mut bevy_app::App) {
|
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(
|
.add_systems(
|
||||||
Render,
|
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_pipelines.in_set(RenderSystems::Prepare),
|
||||||
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
|
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
|
||||||
),
|
),
|
||||||
@ -113,7 +113,6 @@ impl Plugin for TemporalAntiAliasPlugin {
|
|||||||
///
|
///
|
||||||
/// # Usage Notes
|
/// # 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`].
|
/// 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`].
|
/// [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
|
/// 1. Write particle motion vectors to the motion vectors prepass texture
|
||||||
/// 2. Render particles after TAA
|
/// 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)]
|
#[derive(Component, Reflect, Clone)]
|
||||||
#[reflect(Component, Default, Clone)]
|
#[reflect(Component, Default, Clone)]
|
||||||
#[require(TemporalJitter, DepthPrepass, MotionVectorPrepass)]
|
#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass)]
|
||||||
#[doc(alias = "Taa")]
|
#[doc(alias = "Taa")]
|
||||||
pub struct TemporalAntiAliasing {
|
pub struct TemporalAntiAliasing {
|
||||||
/// Set to true to delete the saved temporal history (past frames).
|
/// 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>) {
|
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,
|
RenderEntity,
|
||||||
&Camera,
|
&Camera,
|
||||||
&Projection,
|
&Projection,
|
||||||
&mut TemporalAntiAliasing,
|
Option<&mut TemporalAntiAliasing>,
|
||||||
), (
|
|
||||||
With<Camera3d>,
|
|
||||||
With<TemporalJitter>,
|
|
||||||
With<DepthPrepass>,
|
|
||||||
With<MotionVectorPrepass>,
|
|
||||||
)>();
|
)>();
|
||||||
|
|
||||||
for (entity, camera, camera_projection, mut taa_settings) in
|
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
|
let mut entity_commands = commands
|
||||||
.get_entity(entity)
|
.get_entity(entity)
|
||||||
.expect("Camera entity wasn't synced.");
|
.expect("Camera entity wasn't synced.");
|
||||||
if camera.is_active && has_perspective_projection {
|
if taa_settings.is_some() && camera.is_active && has_perspective_projection {
|
||||||
entity_commands.insert(taa_settings.clone());
|
entity_commands.insert(taa_settings.as_deref().unwrap().clone());
|
||||||
taa_settings.reset = false;
|
taa_settings.as_mut().unwrap().reset = false;
|
||||||
} else {
|
} else {
|
||||||
// TODO: needs better strategy for cleaning up
|
|
||||||
entity_commands.remove::<(
|
entity_commands.remove::<(
|
||||||
TemporalAntiAliasing,
|
TemporalAntiAliasing,
|
||||||
// components added in prepare systems (because `TemporalAntiAliasNode` does not query extracted components)
|
|
||||||
TemporalAntiAliasHistoryTextures,
|
TemporalAntiAliasHistoryTextures,
|
||||||
TemporalAntiAliasPipelineId,
|
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>,
|
frame_count: Res<FrameCount>,
|
||||||
mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With<TemporalAntiAliasing>>,
|
mut query: Query<
|
||||||
mut commands: Commands,
|
&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 = [
|
let halton_sequence = [
|
||||||
|
vec2(0.0, 0.0),
|
||||||
vec2(0.0, -0.16666666),
|
vec2(0.0, -0.16666666),
|
||||||
vec2(-0.25, 0.16666669),
|
vec2(-0.25, 0.16666669),
|
||||||
vec2(0.25, -0.3888889),
|
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.125, -0.2777778),
|
vec2(-0.125, -0.2777778),
|
||||||
vec2(0.375, 0.055555582),
|
vec2(0.375, 0.055555582),
|
||||||
vec2(-0.4375, 0.3888889),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()];
|
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;
|
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"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides core App functionality for Bevy Engine"
|
description = "Provides core App functionality for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
@ -71,6 +71,12 @@ web = [
|
|||||||
"dep:console_error_panic_hook",
|
"dep:console_error_panic_hook",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
hotpatching = [
|
||||||
|
"bevy_ecs/hotpatching",
|
||||||
|
"dep:dioxus-devtools",
|
||||||
|
"dep:crossbeam-channel",
|
||||||
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
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 }
|
tracing = { version = "0.1", default-features = false, optional = true }
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
cfg-if = "1.0.0"
|
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 }
|
ctrlc = { version = "3.4.4", optional = true }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
|
|||||||
@ -1483,8 +1483,8 @@ mod tests {
|
|||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{Event, EventWriter, Events},
|
event::{Event, EventWriter, Events},
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
query::With,
|
query::With,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
||||||
system::{Commands, Query},
|
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))]
|
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
@ -28,21 +28,26 @@ mod main_schedule;
|
|||||||
mod panic_handler;
|
mod panic_handler;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod plugin_group;
|
mod plugin_group;
|
||||||
|
mod propagate;
|
||||||
mod schedule_runner;
|
mod schedule_runner;
|
||||||
mod sub_app;
|
mod sub_app;
|
||||||
mod task_pool_plugin;
|
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;
|
mod terminal_ctrl_c_handler;
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
pub mod hotpatch;
|
||||||
|
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use main_schedule::*;
|
pub use main_schedule::*;
|
||||||
pub use panic_handler::*;
|
pub use panic_handler::*;
|
||||||
pub use plugin::*;
|
pub use plugin::*;
|
||||||
pub use plugin_group::*;
|
pub use plugin_group::*;
|
||||||
|
pub use propagate::*;
|
||||||
pub use schedule_runner::*;
|
pub use schedule_runner::*;
|
||||||
pub use sub_app::*;
|
pub use sub_app::*;
|
||||||
pub use task_pool_plugin::*;
|
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::*;
|
pub use terminal_ctrl_c_handler::*;
|
||||||
|
|
||||||
/// The app prelude.
|
/// 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).
|
//! apps, and automatically configures platform specifics (i.e. Wasm or Android).
|
||||||
//!
|
//!
|
||||||
//! By default, the [`PanicHandlerPlugin`] from this crate is included in Bevy's `DefaultPlugins`.
|
//! 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"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides asset functionality for Bevy Engine"
|
description = "Provides asset functionality for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_asset_macros"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Derive implementations for bevy_asset"
|
description = "Derive implementations for bevy_asset"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -437,6 +437,18 @@ impl<A: Asset> Assets<A> {
|
|||||||
result
|
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.
|
/// 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`].
|
/// 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> {
|
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`].
|
/// 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`].
|
/// 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> {
|
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
|
||||||
let id: AssetId<A> = id.into();
|
let id: AssetId<A> = id.into();
|
||||||
self.duplicate_handles.remove(&id);
|
self.duplicate_handles.remove(&id);
|
||||||
|
|||||||
@ -141,8 +141,8 @@
|
|||||||
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use alloc::{
|
|||||||
vec::Vec,
|
vec::Vec,
|
||||||
};
|
};
|
||||||
use atomicow::CowArc;
|
use atomicow::CowArc;
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::{error::BevyError, world::World};
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
|
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
|
||||||
use core::any::{Any, TypeId};
|
use core::any::{Any, TypeId};
|
||||||
@ -34,7 +34,7 @@ pub trait AssetLoader: Send + Sync + 'static {
|
|||||||
/// The settings type used by this [`AssetLoader`].
|
/// The settings type used by this [`AssetLoader`].
|
||||||
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
|
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
|
||||||
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
|
/// 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`].
|
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
|
||||||
fn load(
|
fn load(
|
||||||
&self,
|
&self,
|
||||||
@ -58,10 +58,7 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
|
|||||||
reader: &'a mut dyn Reader,
|
reader: &'a mut dyn Reader,
|
||||||
meta: &'a dyn AssetMetaDyn,
|
meta: &'a dyn AssetMetaDyn,
|
||||||
load_context: LoadContext<'a>,
|
load_context: LoadContext<'a>,
|
||||||
) -> BoxedFuture<
|
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
|
||||||
'a,
|
|
||||||
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
|
||||||
>;
|
|
||||||
|
|
||||||
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
|
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
|
||||||
fn extensions(&self) -> &[&str];
|
fn extensions(&self) -> &[&str];
|
||||||
@ -89,10 +86,7 @@ where
|
|||||||
reader: &'a mut dyn Reader,
|
reader: &'a mut dyn Reader,
|
||||||
meta: &'a dyn AssetMetaDyn,
|
meta: &'a dyn AssetMetaDyn,
|
||||||
mut load_context: LoadContext<'a>,
|
mut load_context: LoadContext<'a>,
|
||||||
) -> BoxedFuture<
|
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
|
||||||
'a,
|
|
||||||
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
|
||||||
> {
|
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let settings = meta
|
let settings = meta
|
||||||
.loader_settings()
|
.loader_settings()
|
||||||
@ -394,15 +388,15 @@ impl<'a> LoadContext<'a> {
|
|||||||
/// result with [`LoadContext::add_labeled_asset`].
|
/// result with [`LoadContext::add_labeled_asset`].
|
||||||
///
|
///
|
||||||
/// See [`AssetPath`] for more on labeled assets.
|
/// See [`AssetPath`] for more on labeled assets.
|
||||||
pub fn labeled_asset_scope<A: Asset>(
|
pub fn labeled_asset_scope<A: Asset, E>(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: String,
|
label: String,
|
||||||
load: impl FnOnce(&mut LoadContext) -> A,
|
load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
|
||||||
) -> Handle<A> {
|
) -> Result<Handle<A>, E> {
|
||||||
let mut context = self.begin_labeled_asset();
|
let mut context = self.begin_labeled_asset();
|
||||||
let asset = load(&mut context);
|
let asset = load(&mut context)?;
|
||||||
let loaded_asset = context.finish(asset);
|
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.
|
/// 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.
|
/// See [`AssetPath`] for more on labeled assets.
|
||||||
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
|
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.
|
/// 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 {
|
pub struct AssetLoaderError {
|
||||||
path: AssetPath<'static>,
|
path: AssetPath<'static>,
|
||||||
loader_name: &'static str,
|
loader_name: &'static str,
|
||||||
error: Arc<dyn core::error::Error + Send + Sync + 'static>,
|
error: Arc<BevyError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetLoaderError {
|
impl AssetLoaderError {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_audio"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides audio functionality for Bevy Engine"
|
description = "Provides audio functionality for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
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" }
|
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
|
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
|
||||||
rodio = { version = "0.20", default-features = false }
|
rodio = { version = "0.20", default-features = false }
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
cpal = { version = "0.15", optional = true }
|
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]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||||
rodio = { version = "0.20", default-features = false, features = [
|
rodio = { version = "0.20", default-features = false, features = [
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
//! Audio support for the game engine Bevy
|
//! Audio support for the game engine Bevy
|
||||||
|
|||||||
@ -34,7 +34,7 @@ impl GlobalVolume {
|
|||||||
#[derive(Clone, Copy, Debug, Reflect)]
|
#[derive(Clone, Copy, Debug, Reflect)]
|
||||||
#[reflect(Clone, Debug, PartialEq)]
|
#[reflect(Clone, Debug, PartialEq)]
|
||||||
pub enum Volume {
|
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,
|
/// In a linear scale, the value `1.0` represents the "normal" volume,
|
||||||
/// meaning the audio is played at its original level. Values greater than
|
/// 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.
|
/// 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.
|
/// is `0.0`, this method returns negative infinity.
|
||||||
pub fn to_decibels(&self) -> f32 {
|
pub fn to_decibels(&self) -> f32 {
|
||||||
match self {
|
match self {
|
||||||
@ -155,57 +155,95 @@ impl Volume {
|
|||||||
|
|
||||||
/// The silent volume. Also known as "off" or "muted".
|
/// The silent volume. Also known as "off" or "muted".
|
||||||
pub const SILENT: Self = Volume::Linear(0.0);
|
pub const SILENT: Self = Volume::Linear(0.0);
|
||||||
}
|
|
||||||
|
|
||||||
impl core::ops::Add<Self> for Volume {
|
/// Increases the volume by the specified percentage.
|
||||||
type Output = Self;
|
///
|
||||||
|
/// This method works in the linear domain, where a 100% increase
|
||||||
fn add(self, rhs: Self) -> Self {
|
/// means doubling the volume (equivalent to +6.02dB).
|
||||||
use Volume::{Decibels, Linear};
|
///
|
||||||
|
/// # Arguments
|
||||||
match (self, rhs) {
|
/// * `percentage` - The percentage to increase (50.0 means 50% increase)
|
||||||
(Linear(a), Linear(b)) => Linear(a + b),
|
///
|
||||||
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
|
/// # Examples
|
||||||
decibels_to_linear(a) + decibels_to_linear(b),
|
/// ```
|
||||||
)),
|
/// use bevy_audio::Volume;
|
||||||
// {Linear, Decibels} favors the left hand side of the operation by
|
///
|
||||||
// first converting the right hand side to the same type as the left
|
/// let volume = Volume::Linear(1.0);
|
||||||
// hand side and then performing the operation.
|
/// let increased = volume.increase_by_percentage(100.0);
|
||||||
(Linear(..), Decibels(db)) => self + Linear(decibels_to_linear(db)),
|
/// assert_eq!(increased.to_linear(), 2.0);
|
||||||
(Decibels(..), Linear(l)) => self + Decibels(linear_to_decibels(l)),
|
/// ```
|
||||||
}
|
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 {
|
/// Decreases the volume by the specified percentage.
|
||||||
fn add_assign(&mut self, rhs: Self) {
|
///
|
||||||
*self = *self + rhs;
|
/// 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 {
|
/// Scales the volume to a specific linear factor relative to the current volume.
|
||||||
type Output = Self;
|
///
|
||||||
|
/// This is different from `adjust_by_linear` as it sets the volume to be
|
||||||
fn sub(self, rhs: Self) -> Self {
|
/// exactly the factor times the original volume, rather than applying
|
||||||
use Volume::{Decibels, Linear};
|
/// the factor to the current volume.
|
||||||
|
///
|
||||||
match (self, rhs) {
|
/// # Arguments
|
||||||
(Linear(a), Linear(b)) => Linear(a - b),
|
/// * `factor` - The scaling factor (2.0 = twice as loud, 0.5 = half as loud)
|
||||||
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
|
///
|
||||||
decibels_to_linear(a) - decibels_to_linear(b),
|
/// # Examples
|
||||||
)),
|
/// ```
|
||||||
// {Linear, Decibels} favors the left hand side of the operation by
|
/// use bevy_audio::Volume;
|
||||||
// first converting the right hand side to the same type as the left
|
///
|
||||||
// hand side and then performing the operation.
|
/// let volume = Volume::Linear(0.8);
|
||||||
(Linear(..), Decibels(db)) => self - Linear(decibels_to_linear(db)),
|
/// let scaled = volume.scale_to_factor(1.25);
|
||||||
(Decibels(..), Linear(l)) => self - Decibels(linear_to_decibels(l)),
|
/// 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 {
|
/// Creates a fade effect by interpolating between current volume and target volume.
|
||||||
fn sub_assign(&mut self, rhs: Self) {
|
///
|
||||||
*self = *self - rhs;
|
/// 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(),
|
Linear(f32::NEG_INFINITY).to_decibels().is_infinite(),
|
||||||
"Negative infinite linear scale is equivalent to infinite decibels"
|
"Negative infinite linear scale is equivalent to infinite decibels"
|
||||||
);
|
);
|
||||||
assert!(
|
assert_eq!(
|
||||||
Decibels(f32::NEG_INFINITY).to_linear().abs() == 0.0,
|
Decibels(f32::NEG_INFINITY).to_linear().abs(),
|
||||||
|
0.0,
|
||||||
"Negative infinity decibels is equivalent to zero linear scale"
|
"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) {
|
fn assert_approx_eq(a: Volume, b: Volume) {
|
||||||
const EPSILON: f32 = 0.0001;
|
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]
|
#[test]
|
||||||
fn volume_ops_mul() {
|
fn volume_ops_mul() {
|
||||||
// Linear to Linear.
|
// Linear to Linear.
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_color"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Types for representing and manipulating color values"
|
description = "Types for representing and manipulating color values"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy", "color"]
|
keywords = ["bevy", "color"]
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
@ -262,6 +262,7 @@ macro_rules! impl_componentwise_vector_space {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl bevy_math::VectorSpace for $ty {
|
impl bevy_math::VectorSpace for $ty {
|
||||||
|
type Scalar = f32;
|
||||||
const ZERO: Self = Self {
|
const ZERO: Self = Self {
|
||||||
$($element: 0.0,)+
|
$($element: 0.0,)+
|
||||||
};
|
};
|
||||||
|
|||||||
@ -7,7 +7,7 @@ authors = [
|
|||||||
"Carter Anderson <mcanders1@gmail.com>",
|
"Carter Anderson <mcanders1@gmail.com>",
|
||||||
]
|
]
|
||||||
description = "Provides a core render pipeline for Bevy Engine."
|
description = "Provides a core render pipeline for Bevy Engine."
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
pub mod auto_exposure;
|
pub mod auto_exposure;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
|
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::{component::*, prelude::*};
|
use bevy_ecs::{component::*, lifecycle::ComponentHook, prelude::*};
|
||||||
use bevy_math::UVec2;
|
use bevy_math::UVec2;
|
||||||
use bevy_platform::collections::HashSet;
|
use bevy_platform::collections::HashSet;
|
||||||
use bevy_platform::time::Instant;
|
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"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides derive implementations for Bevy Engine"
|
description = "Provides derive implementations for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
name = "bevy_derive_compile_fail"
|
name = "bevy_derive_compile_fail"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Compile fail tests for Bevy Engine's various macros"
|
description = "Compile fail tests for Bevy Engine's various macros"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
publish = false
|
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)]
|
#![forbid(unsafe_code)]
|
||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
@ -188,11 +189,34 @@ pub fn derive_deref_mut(input: TokenStream) -> TokenStream {
|
|||||||
derefs::derive_deref_mut(input)
|
derefs::derive_deref_mut(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates the required main function boilerplate for Android.
|
||||||
#[proc_macro_attribute]
|
#[proc_macro_attribute]
|
||||||
pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream {
|
pub fn bevy_main(attr: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
bevy_main::bevy_main(attr, item)
|
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)]
|
#[proc_macro_derive(EnumVariantMeta)]
|
||||||
pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
|
pub fn derive_enum_variant_meta(input: TokenStream) -> TokenStream {
|
||||||
enum_variant_meta::derive_enum_variant_meta(input)
|
enum_variant_meta::derive_enum_variant_meta(input)
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_dev_tools"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Collection of developer tools for the Bevy Engine"
|
description = "Collection of developer tools for the Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.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.
|
//! focused on improving developer experience.
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
|
|||||||
@ -94,8 +94,8 @@ impl Plugin for DebugPickingPlugin {
|
|||||||
log_event_debug::<pointer::PointerInput>.run_if(DebugPickingMode::is_noisy),
|
log_event_debug::<pointer::PointerInput>.run_if(DebugPickingMode::is_noisy),
|
||||||
log_pointer_event_debug::<Over>,
|
log_pointer_event_debug::<Over>,
|
||||||
log_pointer_event_debug::<Out>,
|
log_pointer_event_debug::<Out>,
|
||||||
log_pointer_event_debug::<Pressed>,
|
log_pointer_event_debug::<Press>,
|
||||||
log_pointer_event_debug::<Released>,
|
log_pointer_event_debug::<Release>,
|
||||||
log_pointer_event_debug::<Click>,
|
log_pointer_event_debug::<Click>,
|
||||||
log_pointer_event_trace::<Move>.run_if(DebugPickingMode::is_noisy),
|
log_pointer_event_trace::<Move>.run_if(DebugPickingMode::is_noisy),
|
||||||
log_pointer_event_debug::<DragStart>,
|
log_pointer_event_debug::<DragStart>,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_diagnostic"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides diagnostic functionality for Bevy Engine"
|
description = "Provides diagnostic functionality for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -19,8 +19,10 @@ impl Plugin for EntityCountDiagnosticsPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl EntityCountDiagnosticsPlugin {
|
impl EntityCountDiagnosticsPlugin {
|
||||||
|
/// Number of currently allocated entities.
|
||||||
pub const ENTITY_COUNT: DiagnosticPath = DiagnosticPath::const_new("entity_count");
|
pub const ENTITY_COUNT: DiagnosticPath = DiagnosticPath::const_new("entity_count");
|
||||||
|
|
||||||
|
/// Updates entity count measurement.
|
||||||
pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) {
|
pub fn diagnostic_system(mut diagnostics: Diagnostics, entities: &Entities) {
|
||||||
diagnostics.add_measurement(&Self::ENTITY_COUNT, || entities.count_constructed() as f64);
|
diagnostics.add_measurement(&Self::ENTITY_COUNT, || entities.count_constructed() as f64);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,10 +58,16 @@ impl Plugin for FrameTimeDiagnosticsPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FrameTimeDiagnosticsPlugin {
|
impl FrameTimeDiagnosticsPlugin {
|
||||||
|
/// Frames per second.
|
||||||
pub const FPS: DiagnosticPath = DiagnosticPath::const_new("fps");
|
pub const FPS: DiagnosticPath = DiagnosticPath::const_new("fps");
|
||||||
|
|
||||||
|
/// Total frames since application start.
|
||||||
pub const FRAME_COUNT: DiagnosticPath = DiagnosticPath::const_new("frame_count");
|
pub const FRAME_COUNT: DiagnosticPath = DiagnosticPath::const_new("frame_count");
|
||||||
|
|
||||||
|
/// Frame time in ms.
|
||||||
pub const FRAME_TIME: DiagnosticPath = DiagnosticPath::const_new("frame_time");
|
pub const FRAME_TIME: DiagnosticPath = DiagnosticPath::const_new("frame_time");
|
||||||
|
|
||||||
|
/// Updates frame count, frame time and fps measurements.
|
||||||
pub fn diagnostic_system(
|
pub fn diagnostic_system(
|
||||||
mut diagnostics: Diagnostics,
|
mut diagnostics: Diagnostics,
|
||||||
time: Res<Time<Real>>,
|
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))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
#![no_std]
|
#![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
|
//! It allows users to easily add diagnostic functionality to their Bevy applications, enhancing
|
||||||
//! their ability to monitor and optimize their game's.
|
//! their ability to monitor and optimize their game's.
|
||||||
|
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_dylib"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Force the Bevy Engine to be dynamically linked for faster linking"
|
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"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
keywords = ["bevy"]
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
|
|
||||||
//! Forces dynamic linking of Bevy.
|
//! Forces dynamic linking of Bevy.
|
||||||
|
|||||||
@ -3,7 +3,7 @@ name = "bevy_ecs"
|
|||||||
version = "0.16.0-dev"
|
version = "0.16.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Bevy Engine's entity component system"
|
description = "Bevy Engine's entity component system"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevy.org"
|
||||||
repository = "https://github.com/bevyengine/bevy"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["ecs", "game", "bevy"]
|
keywords = ["ecs", "game", "bevy"]
|
||||||
@ -83,6 +83,8 @@ critical-section = [
|
|||||||
"bevy_reflect?/critical-section",
|
"bevy_reflect?/critical-section",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
hotpatching = ["dep:subsecond"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" }
|
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
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 }
|
tracing = { version = "0.1", default-features = false, optional = true }
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
bumpalo = "3"
|
bumpalo = "3"
|
||||||
|
subsecond = { version = "0.7.0-alpha.1", optional = true }
|
||||||
|
|
||||||
concurrent-queue = { version = "2.5.0", default-features = false }
|
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]
|
[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();
|
let entity = world.spawn_empty().id();
|
||||||
|
|
||||||
world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
println!("Entity {} goes BOOM!", trigger.target());
|
println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||||
commands.entity(trigger.target()).despawn();
|
commands.entity(trigger.target().unwrap()).despawn();
|
||||||
});
|
});
|
||||||
|
|
||||||
world.flush();
|
world.flush();
|
||||||
@ -349,4 +349,4 @@ world.flush();
|
|||||||
world.trigger_targets(Explode, entity);
|
world.trigger_targets(Explode, entity);
|
||||||
```
|
```
|
||||||
|
|
||||||
[bevy]: https://bevyengine.org/
|
[bevy]: https://bevy.org/
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
name = "bevy_ecs_compile_fail"
|
name = "bevy_ecs_compile_fail"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Compile fail tests for Bevy Engine's entity component system"
|
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"
|
repository = "https://github.com/bevyengine/bevy"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
publish = false
|
publish = false
|
||||||
|
|||||||
@ -60,4 +60,4 @@ mod case4 {
|
|||||||
pub struct BarTargetOf(Entity);
|
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::Path(path) => path.to_token_stream(),
|
||||||
HookAttributeKind::Call(call) => {
|
HookAttributeKind::Call(call) => {
|
||||||
quote!({
|
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)
|
(#call)(world, ctx)
|
||||||
}
|
}
|
||||||
_internal_hook
|
_internal_hook
|
||||||
@ -658,7 +658,7 @@ fn hook_register_function_call(
|
|||||||
) -> Option<TokenStream2> {
|
) -> Option<TokenStream2> {
|
||||||
function.map(|meta| {
|
function.map(|meta| {
|
||||||
quote! {
|
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)
|
::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))]
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
extern crate proc_macro;
|
extern crate proc_macro;
|
||||||
@ -28,12 +29,48 @@ enum BundleFieldKind {
|
|||||||
|
|
||||||
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
||||||
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
|
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))]
|
#[proc_macro_derive(Bundle, attributes(bundle))]
|
||||||
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
let ast = parse_macro_input!(input as DeriveInput);
|
||||||
let ecs_path = bevy_ecs_path();
|
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)") {
|
let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
|
||||||
Ok(fields) => fields,
|
Ok(fields) => fields,
|
||||||
Err(e) => return e.into_compile_error().into(),
|
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 (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
let struct_name = &ast.ident;
|
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! {
|
TokenStream::from(quote! {
|
||||||
|
#(#attribute_errors)*
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
|
// - 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
|
// - `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:
|
#from_components
|
||||||
// - 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)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
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))]
|
#[proc_macro_derive(MapEntities, attributes(entities))]
|
||||||
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
|
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
|
||||||
let ast = parse_macro_input!(input as DeriveInput);
|
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")
|
BevyManifest::shared().get_path("bevy_ecs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `Event` trait.
|
||||||
#[proc_macro_derive(Event, attributes(event))]
|
#[proc_macro_derive(Event, attributes(event))]
|
||||||
pub fn derive_event(input: TokenStream) -> TokenStream {
|
pub fn derive_event(input: TokenStream) -> TokenStream {
|
||||||
component::derive_event(input)
|
component::derive_event(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `Resource` trait.
|
||||||
#[proc_macro_derive(Resource)]
|
#[proc_macro_derive(Resource)]
|
||||||
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
pub fn derive_resource(input: TokenStream) -> TokenStream {
|
||||||
component::derive_resource(input)
|
component::derive_resource(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `Component` trait.
|
||||||
#[proc_macro_derive(
|
#[proc_macro_derive(
|
||||||
Component,
|
Component,
|
||||||
attributes(component, require, relationship, relationship_target, entities)
|
attributes(component, require, relationship, relationship_target, entities)
|
||||||
@ -540,6 +589,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
component::derive_component(input)
|
component::derive_component(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement the `FromWorld` trait.
|
||||||
#[proc_macro_derive(FromWorld, attributes(from_world))]
|
#[proc_macro_derive(FromWorld, attributes(from_world))]
|
||||||
pub fn derive_from_world(input: TokenStream) -> TokenStream {
|
pub fn derive_from_world(input: TokenStream) -> TokenStream {
|
||||||
let bevy_ecs_path = bevy_ecs_path();
|
let bevy_ecs_path = bevy_ecs_path();
|
||||||
|
|||||||
@ -23,17 +23,22 @@ use crate::{
|
|||||||
bundle::BundleId,
|
bundle::BundleId,
|
||||||
component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
|
component::{ComponentId, Components, RequiredComponentConstructor, StorageType},
|
||||||
entity::{Entity, EntityLocation},
|
entity::{Entity, EntityLocation},
|
||||||
|
event::Event,
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
|
storage::{ImmutableSparseSet, SparseArray, SparseSet, TableId, TableRow},
|
||||||
};
|
};
|
||||||
use alloc::{boxed::Box, vec::Vec};
|
use alloc::{boxed::Box, vec::Vec};
|
||||||
use bevy_platform::collections::HashMap;
|
use bevy_platform::collections::{hash_map::Entry, HashMap};
|
||||||
use core::{
|
use core::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
ops::{Index, IndexMut, RangeFrom},
|
ops::{Index, IndexMut, RangeFrom},
|
||||||
};
|
};
|
||||||
use nonmax::NonMaxU32;
|
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`].
|
/// An opaque location within a [`Archetype`].
|
||||||
///
|
///
|
||||||
/// This can be used in conjunction with [`ArchetypeId`] to find the exact location
|
/// 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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_add_observer(&self) -> bool {
|
pub fn has_add_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_insert_observer(&self) -> bool {
|
pub fn has_insert_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_replace_observer(&self) -> bool {
|
pub fn has_replace_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_remove_observer(&self) -> bool {
|
pub fn has_remove_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
|
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
|
/// 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]
|
#[inline]
|
||||||
pub fn has_despawn_observer(&self) -> bool {
|
pub fn has_despawn_observer(&self) -> bool {
|
||||||
self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER)
|
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.
|
/// 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
|
/// `table_components` and `sparse_set_components` must be sorted
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -881,7 +890,7 @@ impl Archetypes {
|
|||||||
table_id: TableId,
|
table_id: TableId,
|
||||||
table_components: Vec<ComponentId>,
|
table_components: Vec<ComponentId>,
|
||||||
sparse_set_components: Vec<ComponentId>,
|
sparse_set_components: Vec<ComponentId>,
|
||||||
) -> ArchetypeId {
|
) -> (ArchetypeId, bool) {
|
||||||
let archetype_identity = ArchetypeComponents {
|
let archetype_identity = ArchetypeComponents {
|
||||||
sparse_set_components: sparse_set_components.into_boxed_slice(),
|
sparse_set_components: sparse_set_components.into_boxed_slice(),
|
||||||
table_components: table_components.into_boxed_slice(),
|
table_components: table_components.into_boxed_slice(),
|
||||||
@ -889,14 +898,13 @@ impl Archetypes {
|
|||||||
|
|
||||||
let archetypes = &mut self.archetypes;
|
let archetypes = &mut self.archetypes;
|
||||||
let component_index = &mut self.by_component;
|
let component_index = &mut self.by_component;
|
||||||
*self
|
match self.by_components.entry(archetype_identity) {
|
||||||
.by_components
|
Entry::Occupied(occupied) => (*occupied.get(), false),
|
||||||
.entry(archetype_identity)
|
Entry::Vacant(vacant) => {
|
||||||
.or_insert_with_key(move |identity| {
|
|
||||||
let ArchetypeComponents {
|
let ArchetypeComponents {
|
||||||
table_components,
|
table_components,
|
||||||
sparse_set_components,
|
sparse_set_components,
|
||||||
} = identity;
|
} = vacant.key();
|
||||||
let id = ArchetypeId::new(archetypes.len());
|
let id = ArchetypeId::new(archetypes.len());
|
||||||
archetypes.push(Archetype::new(
|
archetypes.push(Archetype::new(
|
||||||
components,
|
components,
|
||||||
@ -907,8 +915,10 @@ impl Archetypes {
|
|||||||
table_components.iter().copied(),
|
table_components.iter().copied(),
|
||||||
sparse_set_components.iter().copied(),
|
sparse_set_components.iter().copied(),
|
||||||
));
|
));
|
||||||
id
|
vacant.insert(id);
|
||||||
})
|
(id, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears all entities from all archetypes.
|
/// Clears all entities from all archetypes.
|
||||||
|
|||||||
@ -2,12 +2,63 @@
|
|||||||
//!
|
//!
|
||||||
//! This module contains the [`Bundle`] trait and some other helper types.
|
//! 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;
|
pub use bevy_ecs_macros::Bundle;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::{
|
archetype::{
|
||||||
Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus,
|
Archetype, ArchetypeAfterBundleInsert, ArchetypeCreated, ArchetypeId, Archetypes,
|
||||||
ComponentStatus, SpawnBundleStatus,
|
BundleComponentStatus, ComponentStatus, SpawnBundleStatus,
|
||||||
},
|
},
|
||||||
change_detection::MaybeLocation,
|
change_detection::MaybeLocation,
|
||||||
component::{
|
component::{
|
||||||
@ -15,15 +66,13 @@ use crate::{
|
|||||||
RequiredComponents, StorageType, Tick,
|
RequiredComponents, StorageType, Tick,
|
||||||
},
|
},
|
||||||
entity::{Entities, EntitiesAllocator, Entity, EntityLocation},
|
entity::{Entities, EntitiesAllocator, Entity, EntityLocation},
|
||||||
|
lifecycle::{ON_ADD, ON_INSERT, ON_REMOVE, ON_REPLACE},
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
prelude::World,
|
prelude::World,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||||
world::{
|
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut},
|
||||||
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
|
|
||||||
ON_REPLACE,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use alloc::{boxed::Box, vec, vec::Vec};
|
use alloc::{boxed::Box, vec, vec::Vec};
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
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
|
/// This could be the same [`ArchetypeId`], in the event that inserting the given bundle
|
||||||
/// does not result in an [`Archetype`] change.
|
/// does not result in an [`Archetype`] change.
|
||||||
///
|
///
|
||||||
@ -747,12 +796,12 @@ impl BundleInfo {
|
|||||||
components: &Components,
|
components: &Components,
|
||||||
observers: &Observers,
|
observers: &Observers,
|
||||||
archetype_id: ArchetypeId,
|
archetype_id: ArchetypeId,
|
||||||
) -> ArchetypeId {
|
) -> (ArchetypeId, bool) {
|
||||||
if let Some(archetype_after_insert_id) = archetypes[archetype_id]
|
if let Some(archetype_after_insert_id) = archetypes[archetype_id]
|
||||||
.edges()
|
.edges()
|
||||||
.get_archetype_after_bundle_insert(self.id)
|
.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_table_components = Vec::new();
|
||||||
let mut new_sparse_set_components = Vec::new();
|
let mut new_sparse_set_components = Vec::new();
|
||||||
@ -806,7 +855,7 @@ impl BundleInfo {
|
|||||||
added,
|
added,
|
||||||
existing,
|
existing,
|
||||||
);
|
);
|
||||||
archetype_id
|
(archetype_id, false)
|
||||||
} else {
|
} else {
|
||||||
let table_id;
|
let table_id;
|
||||||
let table_components;
|
let table_components;
|
||||||
@ -842,13 +891,14 @@ impl BundleInfo {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
// SAFETY: ids in self must be valid
|
// 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,
|
components,
|
||||||
observers,
|
observers,
|
||||||
table_id,
|
table_id,
|
||||||
table_components,
|
table_components,
|
||||||
sparse_set_components,
|
sparse_set_components,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Add an edge from the old archetype to the new archetype.
|
// Add an edge from the old archetype to the new archetype.
|
||||||
archetypes[archetype_id]
|
archetypes[archetype_id]
|
||||||
.edges_mut()
|
.edges_mut()
|
||||||
@ -860,11 +910,11 @@ impl BundleInfo {
|
|||||||
added,
|
added,
|
||||||
existing,
|
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).
|
/// (or `None` if the removal was invalid).
|
||||||
/// This could be the same [`ArchetypeId`], in the event that removing the given bundle
|
/// This could be the same [`ArchetypeId`], in the event that removing the given bundle
|
||||||
/// does not result in an [`Archetype`] change.
|
/// does not result in an [`Archetype`] change.
|
||||||
@ -887,7 +937,7 @@ impl BundleInfo {
|
|||||||
observers: &Observers,
|
observers: &Observers,
|
||||||
archetype_id: ArchetypeId,
|
archetype_id: ArchetypeId,
|
||||||
intersection: bool,
|
intersection: bool,
|
||||||
) -> Option<ArchetypeId> {
|
) -> (Option<ArchetypeId>, bool) {
|
||||||
// Check the archetype graph to see if the bundle has been
|
// Check the archetype graph to see if the bundle has been
|
||||||
// removed from this archetype in the past.
|
// removed from this archetype in the past.
|
||||||
let archetype_after_remove_result = {
|
let archetype_after_remove_result = {
|
||||||
@ -898,9 +948,9 @@ impl BundleInfo {
|
|||||||
edges.get_archetype_after_bundle_take(self.id())
|
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!
|
// This bundle removal result is cached. Just return that!
|
||||||
result
|
(result, false)
|
||||||
} else {
|
} else {
|
||||||
let mut next_table_components;
|
let mut next_table_components;
|
||||||
let mut next_sparse_set_components;
|
let mut next_sparse_set_components;
|
||||||
@ -925,7 +975,7 @@ impl BundleInfo {
|
|||||||
current_archetype
|
current_archetype
|
||||||
.edges_mut()
|
.edges_mut()
|
||||||
.cache_archetype_after_bundle_take(self.id(), None);
|
.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,
|
components,
|
||||||
observers,
|
observers,
|
||||||
next_table_id,
|
next_table_id,
|
||||||
next_table_components,
|
next_table_components,
|
||||||
next_sparse_set_components,
|
next_sparse_set_components,
|
||||||
);
|
);
|
||||||
Some(new_archetype_id)
|
(Some(new_archetype_id), is_new_created)
|
||||||
};
|
};
|
||||||
let current_archetype = &mut archetypes[archetype_id];
|
let current_archetype = &mut archetypes[archetype_id];
|
||||||
// Cache the result in an edge.
|
// Cache the result in an edge.
|
||||||
@ -973,7 +1023,7 @@ impl BundleInfo {
|
|||||||
.edges_mut()
|
.edges_mut()
|
||||||
.cache_archetype_after_bundle_take(self.id(), result);
|
.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
|
// 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_info = world.bundles.get_unchecked(bundle_id);
|
||||||
let bundle_id = bundle_info.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.archetypes,
|
||||||
&mut world.storages,
|
&mut world.storages,
|
||||||
&world.components,
|
&world.components,
|
||||||
&world.observers,
|
&world.observers,
|
||||||
archetype_id,
|
archetype_id,
|
||||||
);
|
);
|
||||||
if new_archetype_id == archetype_id {
|
|
||||||
|
let inserter = if new_archetype_id == archetype_id {
|
||||||
let archetype = &mut world.archetypes[archetype_id];
|
let archetype = &mut world.archetypes[archetype_id];
|
||||||
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
|
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
|
||||||
let archetype_after_insert = unsafe {
|
let archetype_after_insert = unsafe {
|
||||||
@ -1103,7 +1154,15 @@ impl<'w> BundleInserter<'w> {
|
|||||||
world: world.as_unsafe_world_cell(),
|
world: world.as_unsafe_world_cell(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if is_new_created {
|
||||||
|
inserter
|
||||||
|
.world
|
||||||
|
.into_deferred()
|
||||||
|
.trigger(ArchetypeCreated(new_archetype_id));
|
||||||
}
|
}
|
||||||
|
inserter
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
@ -1133,7 +1192,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if archetype.has_replace_observer() {
|
if archetype.has_replace_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REPLACE,
|
ON_REPLACE,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_existing(),
|
archetype_after_insert.iter_existing(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1318,7 +1377,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if new_archetype.has_add_observer() {
|
if new_archetype.has_add_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_ADD,
|
ON_ADD,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_added(),
|
archetype_after_insert.iter_added(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1336,7 +1395,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if new_archetype.has_insert_observer() {
|
if new_archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_inserted(),
|
archetype_after_insert.iter_inserted(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1355,7 +1414,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
if new_archetype.has_insert_observer() {
|
if new_archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
archetype_after_insert.iter_added(),
|
archetype_after_insert.iter_added(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1421,7 +1480,7 @@ impl<'w> BundleRemover<'w> {
|
|||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let bundle_info = world.bundles.get_unchecked(bundle_id);
|
let bundle_info = world.bundles.get_unchecked(bundle_id);
|
||||||
// SAFETY: Caller ensures archetype and bundle ids are correct.
|
// 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(
|
bundle_info.remove_bundle_from_archetype(
|
||||||
&mut world.archetypes,
|
&mut world.archetypes,
|
||||||
&mut world.storages,
|
&mut world.storages,
|
||||||
@ -1429,11 +1488,14 @@ impl<'w> BundleRemover<'w> {
|
|||||||
&world.observers,
|
&world.observers,
|
||||||
archetype_id,
|
archetype_id,
|
||||||
!require_all,
|
!require_all,
|
||||||
)?
|
)
|
||||||
};
|
};
|
||||||
|
let new_archetype_id = new_archetype_id?;
|
||||||
|
|
||||||
if new_archetype_id == archetype_id {
|
if new_archetype_id == archetype_id {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (old_archetype, new_archetype) =
|
let (old_archetype, new_archetype) =
|
||||||
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
|
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
|
||||||
|
|
||||||
@ -1447,13 +1509,20 @@ impl<'w> BundleRemover<'w> {
|
|||||||
Some((old.into(), new.into()))
|
Some((old.into(), new.into()))
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(Self {
|
let remover = Self {
|
||||||
bundle_info: bundle_info.into(),
|
bundle_info: bundle_info.into(),
|
||||||
new_archetype: new_archetype.into(),
|
new_archetype: new_archetype.into(),
|
||||||
old_archetype: old_archetype.into(),
|
old_archetype: old_archetype.into(),
|
||||||
old_and_new_table: tables,
|
old_and_new_table: tables,
|
||||||
world: world.as_unsafe_world_cell(),
|
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.
|
/// 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() {
|
if self.old_archetype.as_ref().has_replace_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REPLACE,
|
ON_REPLACE,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_components_in_archetype(),
|
bundle_components_in_archetype(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1514,7 +1583,7 @@ impl<'w> BundleRemover<'w> {
|
|||||||
if self.old_archetype.as_ref().has_remove_observer() {
|
if self.old_archetype.as_ref().has_remove_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_REMOVE,
|
ON_REMOVE,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_components_in_archetype(),
|
bundle_components_in_archetype(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1675,22 +1744,30 @@ impl<'w> BundleSpawner<'w> {
|
|||||||
change_tick: Tick,
|
change_tick: Tick,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let bundle_info = world.bundles.get_unchecked(bundle_id);
|
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.archetypes,
|
||||||
&mut world.storages,
|
&mut world.storages,
|
||||||
&world.components,
|
&world.components,
|
||||||
&world.observers,
|
&world.observers,
|
||||||
ArchetypeId::EMPTY,
|
ArchetypeId::EMPTY,
|
||||||
);
|
);
|
||||||
|
|
||||||
let archetype = &mut world.archetypes[new_archetype_id];
|
let archetype = &mut world.archetypes[new_archetype_id];
|
||||||
let table = &mut world.storages.tables[archetype.table_id()];
|
let table = &mut world.storages.tables[archetype.table_id()];
|
||||||
Self {
|
let spawner = Self {
|
||||||
bundle_info: bundle_info.into(),
|
bundle_info: bundle_info.into(),
|
||||||
table: table.into(),
|
table: table.into(),
|
||||||
archetype: archetype.into(),
|
archetype: archetype.into(),
|
||||||
change_tick,
|
change_tick,
|
||||||
world: world.as_unsafe_world_cell(),
|
world: world.as_unsafe_world_cell(),
|
||||||
|
};
|
||||||
|
if is_new_created {
|
||||||
|
spawner
|
||||||
|
.world
|
||||||
|
.into_deferred()
|
||||||
|
.trigger(ArchetypeCreated(new_archetype_id));
|
||||||
}
|
}
|
||||||
|
spawner
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -1770,7 +1847,7 @@ impl<'w> BundleSpawner<'w> {
|
|||||||
if archetype.has_add_observer() {
|
if archetype.has_add_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_ADD,
|
ON_ADD,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_info.iter_contributed_components(),
|
bundle_info.iter_contributed_components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -1785,7 +1862,7 @@ impl<'w> BundleSpawner<'w> {
|
|||||||
if archetype.has_insert_observer() {
|
if archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
ON_INSERT,
|
ON_INSERT,
|
||||||
entity,
|
Some(entity),
|
||||||
bundle_info.iter_contributed_components(),
|
bundle_info.iter_contributed_components(),
|
||||||
caller,
|
caller,
|
||||||
);
|
);
|
||||||
@ -2056,7 +2133,9 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{component::HookContext, prelude::*, world::DeferredWorld};
|
use crate::{
|
||||||
|
archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld,
|
||||||
|
};
|
||||||
use alloc::vec;
|
use alloc::vec;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[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]
|
#[test]
|
||||||
fn component_hook_order_spawn_despawn() {
|
fn component_hook_order_spawn_despawn() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
@ -2293,4 +2392,23 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(a, vec![1]);
|
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.
|
/// 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
|
/// This can be used in queries to access change detection from immutable query methods, as opposed
|
||||||
/// `&mut T`, which only provides access to change detection while in its mutable form:
|
/// to `&mut T` which only provides access to change detection from mutable query methods.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use bevy_ecs::prelude::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// # use bevy_ecs::query::QueryData;
|
/// # use bevy_ecs::query::QueryData;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Component, Clone)]
|
/// #[derive(Component, Clone, Debug)]
|
||||||
/// struct Name(String);
|
/// struct Name(String);
|
||||||
///
|
///
|
||||||
/// #[derive(Component, Clone, Copy)]
|
/// #[derive(Component, Clone, Copy, Debug)]
|
||||||
/// struct Health(f32);
|
/// struct Health(f32);
|
||||||
///
|
///
|
||||||
/// #[derive(Component, Clone, Copy)]
|
/// fn my_system(mut query: Query<(Mut<Name>, &mut Health)>) {
|
||||||
/// struct Position {
|
/// // Mutable access provides change detection information for both parameters:
|
||||||
/// x: f32,
|
/// // - `name` has type `Mut<Name>`
|
||||||
/// y: f32,
|
/// // - `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)]
|
/// // Immutable access only provides change detection for `Name`:
|
||||||
/// struct Player {
|
/// // - `name` has type `Ref<Name>`
|
||||||
/// id: usize,
|
/// // - `health` has type `&Health`
|
||||||
/// };
|
/// for (name, health) in query.iter() {
|
||||||
///
|
/// println!("Name: {:?} (last changed {:?})", name, name.last_changed());
|
||||||
/// #[derive(QueryData)]
|
/// println!("Health: {:?}", health);
|
||||||
/// #[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);
|
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// # bevy_ecs::system::assert_is_system(update_player_avatars);
|
/// # bevy_ecs::system::assert_is_system(my_system);
|
||||||
///
|
|
||||||
/// # 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) {}
|
|
||||||
/// ```
|
/// ```
|
||||||
pub struct Mut<'w, T: ?Sized> {
|
pub struct Mut<'w, T: ?Sized> {
|
||||||
pub(crate) value: &'w mut T,
|
pub(crate) value: &'w mut T,
|
||||||
|
|||||||
@ -5,16 +5,17 @@ use crate::{
|
|||||||
bundle::BundleInfo,
|
bundle::BundleInfo,
|
||||||
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
||||||
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
|
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
|
||||||
|
lifecycle::{ComponentHook, ComponentHooks},
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
relationship::RelationshipHookMode,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
||||||
system::{Local, SystemParam},
|
system::{Local, SystemParam},
|
||||||
world::{DeferredWorld, FromWorld, World},
|
world::{FromWorld, World},
|
||||||
};
|
};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use alloc::{borrow::Cow, format, vec::Vec};
|
use alloc::{borrow::Cow, format, vec::Vec};
|
||||||
pub use bevy_ecs_macros::Component;
|
pub use bevy_ecs_macros::Component;
|
||||||
|
use bevy_ecs_macros::Event;
|
||||||
use bevy_platform::sync::Arc;
|
use bevy_platform::sync::Arc;
|
||||||
use bevy_platform::{
|
use bevy_platform::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
@ -375,7 +376,8 @@ use thiserror::Error;
|
|||||||
/// - `#[component(on_remove = on_remove_function)]`
|
/// - `#[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::world::DeferredWorld;
|
||||||
/// # use bevy_ecs::entity::Entity;
|
/// # use bevy_ecs::entity::Entity;
|
||||||
/// # use bevy_ecs::component::ComponentId;
|
/// # use bevy_ecs::component::ComponentId;
|
||||||
@ -404,7 +406,8 @@ use thiserror::Error;
|
|||||||
/// This also supports function calls that yield closures
|
/// 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;
|
/// # use bevy_ecs::world::DeferredWorld;
|
||||||
/// #
|
/// #
|
||||||
/// #[derive(Component)]
|
/// #[derive(Component)]
|
||||||
@ -656,244 +659,6 @@ pub enum StorageType {
|
|||||||
SparseSet,
|
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`].
|
/// Stores metadata for a type of component or resource stored in a specific [`World`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ComponentInfo {
|
pub struct ComponentInfo {
|
||||||
@ -2052,7 +1817,7 @@ impl Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the metadata associated with the given component, if it is registered.
|
/// 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.
|
/// 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]
|
#[inline]
|
||||||
@ -2400,7 +2165,7 @@ impl Components {
|
|||||||
/// * [`World::component_id()`]
|
/// * [`World::component_id()`]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn valid_component_id<T: Component>(&self) -> Option<ComponentId> {
|
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()`].
|
/// Type-erased equivalent of [`Components::valid_resource_id()`].
|
||||||
@ -2431,7 +2196,7 @@ impl Components {
|
|||||||
/// * [`Components::get_resource_id()`]
|
/// * [`Components::get_resource_id()`]
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn valid_resource_id<T: Resource>(&self) -> Option<ComponentId> {
|
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()`].
|
/// Type-erased equivalent of [`Components::component_id()`].
|
||||||
@ -2616,7 +2381,7 @@ impl Tick {
|
|||||||
///
|
///
|
||||||
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
||||||
#[inline]
|
#[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);
|
let age = tick.relative_to(*self);
|
||||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
// 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.
|
// 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.
|
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct TickCells<'a> {
|
pub struct TickCells<'a> {
|
||||||
|
|||||||
@ -711,7 +711,7 @@ impl<'w> EntityClonerBuilder<'w> {
|
|||||||
/// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods.
|
/// [`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 {
|
pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
|
||||||
for type_id in ids {
|
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);
|
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.
|
/// 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 {
|
pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
|
||||||
for type_id in ids {
|
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);
|
self.filter_deny(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -768,7 +768,7 @@ impl<'w> EntityClonerBuilder<'w> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
clone_behavior: ComponentCloneBehavior,
|
clone_behavior: ComponentCloneBehavior,
|
||||||
) -> &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
|
self.entity_cloner
|
||||||
.clone_behavior_overrides
|
.clone_behavior_overrides
|
||||||
.insert(id, clone_behavior);
|
.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.
|
/// 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 {
|
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.entity_cloner.clone_behavior_overrides.remove(&id);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
|
|||||||
@ -357,7 +357,10 @@ mod tests {
|
|||||||
// Next allocated entity should be a further generation on the same index
|
// Next allocated entity should be a further generation on the same index
|
||||||
let entity = world.spawn_empty().id();
|
let entity = world.spawn_empty().id();
|
||||||
assert_eq!(entity.index(), dead_ref.index());
|
assert_eq!(entity.index(), dead_ref.index());
|
||||||
assert!(entity.generation() > dead_ref.generation());
|
assert!(entity
|
||||||
|
.generation()
|
||||||
|
.cmp_approx(&dead_ref.generation())
|
||||||
|
.is_gt());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -372,6 +375,9 @@ mod tests {
|
|||||||
// Next allocated entity should be a further generation on the same index
|
// Next allocated entity should be a further generation on the same index
|
||||||
let entity = world.spawn_empty().id();
|
let entity = world.spawn_empty().id();
|
||||||
assert_eq!(entity.index(), dead_ref.index());
|
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.
|
/// 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
|
/// # Aliasing
|
||||||
///
|
///
|
||||||
/// Internally [`EntityGeneration`] wraps a `u32`, so it can't represent *every* possible generation.
|
/// 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`].
|
/// Represents the first generation of an [`EntityRow`].
|
||||||
pub const FIRST: Self = Self(0);
|
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.
|
/// Gets some bits that represent this value.
|
||||||
/// The bits are opaque and should not be regarded as meaningful.
|
/// The bits are opaque and should not be regarded as meaningful.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -247,18 +227,48 @@ impl EntityGeneration {
|
|||||||
let raw = self.0.overflowing_add(versions);
|
let raw = self.0.overflowing_add(versions);
|
||||||
(Self(raw.0), raw.1)
|
(Self(raw.0), raw.1)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for EntityGeneration {
|
/// Compares two generations.
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
|
///
|
||||||
Some(self.cmp(other))
|
/// Generations that are later will be [`Greater`](core::cmp::Ordering::Greater) than earlier ones.
|
||||||
}
|
///
|
||||||
}
|
/// ```
|
||||||
|
/// # use bevy_ecs::entity::EntityGeneration;
|
||||||
impl Ord for EntityGeneration {
|
/// # use core::cmp::Ordering;
|
||||||
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
/// let later_generation = EntityGeneration::FIRST.after_versions(400);
|
||||||
let diff = self.0.wrapping_sub(other.0);
|
/// assert_eq!(EntityGeneration::FIRST.cmp_approx(&later_generation), Ordering::Less);
|
||||||
(1u32 << 31).cmp(&diff)
|
///
|
||||||
|
/// 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]
|
#[test]
|
||||||
fn entity_debug() {
|
fn entity_debug() {
|
||||||
let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap()));
|
let entity = Entity::from_raw(EntityRow::new(NonMaxU32::new(42).unwrap()));
|
||||||
|
|||||||
@ -68,7 +68,7 @@ pub trait Event: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// # Warning
|
/// # 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).
|
/// and should always correspond to the implementation of [`component_id`](Event::component_id).
|
||||||
fn register_component_id(world: &mut World) -> ComponentId {
|
fn register_component_id(world: &mut World) -> ComponentId {
|
||||||
world.register_component::<EventWrapperComponent<Self>>()
|
world.register_component::<EventWrapperComponent<Self>>()
|
||||||
@ -82,7 +82,7 @@ pub trait Event: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// # Warning
|
/// # 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).
|
/// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id).
|
||||||
fn component_id(world: &World) -> Option<ComponentId> {
|
fn component_id(world: &World) -> Option<ComponentId> {
|
||||||
world.component_id::<EventWrapperComponent<Self>>()
|
world.component_id::<EventWrapperComponent<Self>>()
|
||||||
|
|||||||
@ -10,8 +10,9 @@
|
|||||||
use crate::reflect::{ReflectComponent, ReflectFromWorld};
|
use crate::reflect::{ReflectComponent, ReflectFromWorld};
|
||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
component::{Component, HookContext},
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
lifecycle::HookContext,
|
||||||
relationship::{RelatedSpawner, RelatedSpawnerCommands},
|
relationship::{RelatedSpawner, RelatedSpawnerCommands},
|
||||||
system::EntityCommands,
|
system::EntityCommands,
|
||||||
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
|
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
|
||||||
@ -440,7 +441,7 @@ pub fn validate_parent_has_component<C: Component>(
|
|||||||
let name: Option<String> = None;
|
let name: Option<String> = None;
|
||||||
warn!(
|
warn!(
|
||||||
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
|
"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(),
|
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
|
||||||
ty_name = ShortName::of::<C>(),
|
ty_name = ShortName::of::<C>(),
|
||||||
name = name.map_or_else(
|
name = name.map_or_else(
|
||||||
|
|||||||
@ -13,8 +13,8 @@
|
|||||||
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
|
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
|
||||||
#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")]
|
#![expect(unsafe_code, reason = "Unsafe code is used to improve performance.")]
|
||||||
#![doc(
|
#![doc(
|
||||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||||
)]
|
)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ pub mod event;
|
|||||||
pub mod hierarchy;
|
pub mod hierarchy;
|
||||||
pub mod intern;
|
pub mod intern;
|
||||||
pub mod label;
|
pub mod label;
|
||||||
|
pub mod lifecycle;
|
||||||
pub mod name;
|
pub mod name;
|
||||||
pub mod never;
|
pub mod never;
|
||||||
pub mod observer;
|
pub mod observer;
|
||||||
@ -48,7 +49,6 @@ pub mod query;
|
|||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
pub mod reflect;
|
pub mod reflect;
|
||||||
pub mod relationship;
|
pub mod relationship;
|
||||||
pub mod removal_detection;
|
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
pub mod schedule;
|
pub mod schedule;
|
||||||
pub mod spawn;
|
pub mod spawn;
|
||||||
@ -59,6 +59,9 @@ pub mod world;
|
|||||||
|
|
||||||
pub use bevy_ptr as ptr;
|
pub use bevy_ptr as ptr;
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
use event::Event;
|
||||||
|
|
||||||
/// The ECS prelude.
|
/// The ECS prelude.
|
||||||
///
|
///
|
||||||
/// This includes the most common types in this crate, re-exported for your convenience.
|
/// This includes the most common types in this crate, re-exported for your convenience.
|
||||||
@ -73,12 +76,12 @@ pub mod prelude {
|
|||||||
error::{BevyError, Result},
|
error::{BevyError, Result},
|
||||||
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
||||||
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
||||||
|
lifecycle::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, RemovedComponents},
|
||||||
name::{Name, NameOrEntity},
|
name::{Name, NameOrEntity},
|
||||||
observer::{Observer, Trigger},
|
observer::{Observer, Trigger},
|
||||||
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
||||||
related,
|
related,
|
||||||
relationship::RelationshipTarget,
|
relationship::RelationshipTarget,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{
|
schedule::{
|
||||||
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
|
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
|
||||||
@ -93,7 +96,7 @@ pub mod prelude {
|
|||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
|
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;
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
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::{
|
use crate::{
|
||||||
component::{
|
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
|
||||||
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
|
|
||||||
},
|
|
||||||
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
||||||
|
lifecycle::{ComponentHook, HookContext},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
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 entity_observer;
|
||||||
mod runner;
|
mod runner;
|
||||||
@ -29,6 +161,17 @@ use smallvec::SmallVec;
|
|||||||
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
|
/// 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
|
/// [`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.
|
/// 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 = ()> {
|
pub struct Trigger<'w, E, B: Bundle = ()> {
|
||||||
event: &'w mut E,
|
event: &'w mut E,
|
||||||
propagate: &'w mut bool,
|
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
|
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer. It may
|
||||||
/// be [`Entity::PLACEHOLDER`].
|
/// be [`None`] if the trigger is not for a particular entity.
|
||||||
///
|
pub fn target(&self) -> Option<Entity> {
|
||||||
/// 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 {
|
|
||||||
self.trigger.target
|
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
|
/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination
|
||||||
/// will run.
|
/// 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 {
|
pub trait TriggerTargets {
|
||||||
/// The components the trigger should target.
|
/// The components the trigger should target.
|
||||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_;
|
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_;
|
||||||
@ -280,7 +415,9 @@ all_tuples!(
|
|||||||
T
|
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)]
|
#[derive(Default, Clone)]
|
||||||
pub struct ObserverDescriptor {
|
pub struct ObserverDescriptor {
|
||||||
/// The events the observer is watching.
|
/// 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)]
|
#[derive(Debug)]
|
||||||
pub struct ObserverTrigger {
|
pub struct ObserverTrigger {
|
||||||
/// The [`Entity`] of the observer handling the trigger.
|
/// The [`Entity`] of the observer handling the trigger.
|
||||||
@ -341,7 +480,7 @@ pub struct ObserverTrigger {
|
|||||||
/// The [`ComponentId`]s the trigger targeted.
|
/// The [`ComponentId`]s the trigger targeted.
|
||||||
components: SmallVec<[ComponentId; 2]>,
|
components: SmallVec<[ComponentId; 2]>,
|
||||||
/// The entity the trigger targeted.
|
/// The entity the trigger targeted.
|
||||||
pub target: Entity,
|
pub target: Option<Entity>,
|
||||||
/// The location of the source code that triggered the observer.
|
/// The location of the source code that triggered the observer.
|
||||||
pub caller: MaybeLocation,
|
pub caller: MaybeLocation,
|
||||||
}
|
}
|
||||||
@ -357,6 +496,8 @@ impl ObserverTrigger {
|
|||||||
type ObserverMap = EntityHashMap<ObserverRunner>;
|
type ObserverMap = EntityHashMap<ObserverRunner>;
|
||||||
|
|
||||||
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component.
|
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component.
|
||||||
|
///
|
||||||
|
/// This is stored inside of [`CachedObservers`].
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
pub struct CachedComponentObservers {
|
pub struct CachedComponentObservers {
|
||||||
// Observers listening to triggers targeting this component
|
// Observers listening to triggers targeting this component
|
||||||
@ -366,6 +507,8 @@ pub struct CachedComponentObservers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger.
|
/// 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)]
|
#[derive(Default, Debug)]
|
||||||
pub struct CachedObservers {
|
pub struct CachedObservers {
|
||||||
// Observers listening for any time this trigger is fired
|
// Observers listening for any time this trigger is fired
|
||||||
@ -376,7 +519,13 @@ pub struct CachedObservers {
|
|||||||
entity_observers: EntityHashMap<ObserverMap>,
|
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)]
|
#[derive(Default, Debug)]
|
||||||
pub struct Observers {
|
pub struct Observers {
|
||||||
// Cached ECS observers to save a lookup most common triggers.
|
// Cached ECS observers to save a lookup most common triggers.
|
||||||
@ -385,12 +534,14 @@ pub struct Observers {
|
|||||||
on_replace: CachedObservers,
|
on_replace: CachedObservers,
|
||||||
on_remove: CachedObservers,
|
on_remove: CachedObservers,
|
||||||
on_despawn: 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>,
|
cache: HashMap<ComponentId, CachedObservers>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Observers {
|
impl Observers {
|
||||||
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
||||||
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
ON_ADD => &mut self.on_add,
|
ON_ADD => &mut self.on_add,
|
||||||
ON_INSERT => &mut self.on_insert,
|
ON_INSERT => &mut self.on_insert,
|
||||||
@ -402,6 +553,8 @@ impl Observers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
||||||
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
ON_ADD => Some(&self.on_add),
|
ON_ADD => Some(&self.on_add),
|
||||||
ON_INSERT => Some(&self.on_insert),
|
ON_INSERT => Some(&self.on_insert),
|
||||||
@ -416,7 +569,7 @@ impl Observers {
|
|||||||
pub(crate) fn invoke<T>(
|
pub(crate) fn invoke<T>(
|
||||||
mut world: DeferredWorld,
|
mut world: DeferredWorld,
|
||||||
event_type: ComponentId,
|
event_type: ComponentId,
|
||||||
target: Entity,
|
target: Option<Entity>,
|
||||||
components: impl Iterator<Item = ComponentId> + Clone,
|
components: impl Iterator<Item = ComponentId> + Clone,
|
||||||
data: &mut T,
|
data: &mut T,
|
||||||
propagate: &mut bool,
|
propagate: &mut bool,
|
||||||
@ -455,8 +608,8 @@ impl Observers {
|
|||||||
observers.map.iter().for_each(&mut trigger_observer);
|
observers.map.iter().for_each(&mut trigger_observer);
|
||||||
|
|
||||||
// Trigger entity observers listening for this kind of trigger
|
// Trigger entity observers listening for this kind of trigger
|
||||||
if target != Entity::PLACEHOLDER {
|
if let Some(target_entity) = target {
|
||||||
if let Some(map) = observers.entity_observers.get(&target) {
|
if let Some(map) = observers.entity_observers.get(&target_entity) {
|
||||||
map.iter().for_each(&mut trigger_observer);
|
map.iter().for_each(&mut trigger_observer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -469,8 +622,8 @@ impl Observers {
|
|||||||
.iter()
|
.iter()
|
||||||
.for_each(&mut trigger_observer);
|
.for_each(&mut trigger_observer);
|
||||||
|
|
||||||
if target != Entity::PLACEHOLDER {
|
if let Some(target_entity) = target {
|
||||||
if let Some(map) = component_observers.entity_map.get(&target) {
|
if let Some(map) = component_observers.entity_map.get(&target_entity) {
|
||||||
map.iter().for_each(&mut trigger_observer);
|
map.iter().for_each(&mut trigger_observer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -479,6 +632,8 @@ impl Observers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
||||||
|
use crate::lifecycle::*;
|
||||||
|
|
||||||
match event_type {
|
match event_type {
|
||||||
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
||||||
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
||||||
@ -695,7 +850,7 @@ impl World {
|
|||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, E::Traversal>(
|
world.trigger_observers_with_data::<_, E::Traversal>(
|
||||||
event_id,
|
event_id,
|
||||||
Entity::PLACEHOLDER,
|
None,
|
||||||
targets.components(),
|
targets.components(),
|
||||||
event_data,
|
event_data,
|
||||||
false,
|
false,
|
||||||
@ -708,7 +863,7 @@ impl World {
|
|||||||
unsafe {
|
unsafe {
|
||||||
world.trigger_observers_with_data::<_, E::Traversal>(
|
world.trigger_observers_with_data::<_, E::Traversal>(
|
||||||
event_id,
|
event_id,
|
||||||
target_entity,
|
Some(target_entity),
|
||||||
targets.components(),
|
targets.components(),
|
||||||
event_data,
|
event_data,
|
||||||
E::AUTO_PROPAGATE,
|
E::AUTO_PROPAGATE,
|
||||||
@ -999,20 +1154,20 @@ mod tests {
|
|||||||
world.add_observer(
|
world.add_observer(
|
||||||
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
|obs: Trigger<OnAdd, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||||
res.observed("add_a");
|
res.observed("add_a");
|
||||||
commands.entity(obs.target()).insert(B);
|
commands.entity(obs.target().unwrap()).insert(B);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
|obs: Trigger<OnRemove, A>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||||
res.observed("remove_a");
|
res.observed("remove_a");
|
||||||
commands.entity(obs.target()).remove::<B>();
|
commands.entity(obs.target().unwrap()).remove::<B>();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
|obs: Trigger<OnAdd, B>, mut res: ResMut<Order>, mut commands: Commands| {
|
||||||
res.observed("add_b");
|
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>| {
|
world.add_observer(|_: Trigger<OnRemove, B>, mut res: ResMut<Order>| {
|
||||||
@ -1181,7 +1336,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
world.spawn_empty().observe(system);
|
world.spawn_empty().observe(system);
|
||||||
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
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");
|
res.observed("event_a");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1208,7 +1363,7 @@ mod tests {
|
|||||||
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
.observe(|_: Trigger<EventA>, mut res: ResMut<Order>| res.observed("a_1"))
|
||||||
.id();
|
.id();
|
||||||
world.add_observer(move |obs: Trigger<EventA>, mut res: ResMut<Order>| {
|
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");
|
res.observed("a_2");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1628,7 +1783,7 @@ mod tests {
|
|||||||
|
|
||||||
world.add_observer(
|
world.add_observer(
|
||||||
|trigger: Trigger<EventPropagating>, query: Query<&A>, mut res: ResMut<Order>| {
|
|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");
|
res.observed("event");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1651,7 +1806,7 @@ mod tests {
|
|||||||
fn observer_modifies_relationship() {
|
fn observer_modifies_relationship() {
|
||||||
fn on_add(trigger: Trigger<OnAdd, A>, mut commands: Commands) {
|
fn on_add(trigger: Trigger<OnAdd, A>, mut commands: Commands) {
|
||||||
commands
|
commands
|
||||||
.entity(trigger.target())
|
.entity(trigger.target().unwrap())
|
||||||
.with_related_entities::<crate::hierarchy::ChildOf>(|rsc| {
|
.with_related_entities::<crate::hierarchy::ChildOf>(|rsc| {
|
||||||
rsc.spawn_empty();
|
rsc.spawn_empty();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,8 +2,9 @@ use alloc::{boxed::Box, vec};
|
|||||||
use core::any::Any;
|
use core::any::Any;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
|
component::{ComponentId, Mutable, StorageType},
|
||||||
error::{ErrorContext, ErrorHandler},
|
error::{ErrorContext, ErrorHandler},
|
||||||
|
lifecycle::{ComponentHook, HookContext},
|
||||||
observer::{ObserverDescriptor, ObserverTrigger},
|
observer::{ObserverDescriptor, ObserverTrigger},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
@ -123,8 +124,8 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
|||||||
/// struct Explode;
|
/// struct Explode;
|
||||||
///
|
///
|
||||||
/// world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
/// world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
/// println!("Entity {} goes BOOM!", trigger.target());
|
/// println!("Entity {} goes BOOM!", trigger.target().unwrap());
|
||||||
/// commands.entity(trigger.target()).despawn();
|
/// commands.entity(trigger.target().unwrap()).despawn();
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// world.flush();
|
/// world.flush();
|
||||||
@ -157,7 +158,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
|
|||||||
/// # struct Explode;
|
/// # struct Explode;
|
||||||
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
||||||
/// println!("Boom!");
|
/// println!("Boom!");
|
||||||
/// commands.entity(trigger.target()).despawn();
|
/// commands.entity(trigger.target().unwrap()).despawn();
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
|
/// 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
|
// and is never exclusive
|
||||||
// - system is the same type erased system from above
|
// - system is the same type erased system from above
|
||||||
unsafe {
|
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) {
|
match (*system).validate_param_unsafe(world) {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
if let Err(err) = (*system).run_unsafe(trigger, world) {
|
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
|
/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters
|
||||||
/// erased.
|
/// erased.
|
||||||
|
|||||||
@ -47,6 +47,8 @@ use variadics_please::all_tuples;
|
|||||||
/// - **[`Ref`].**
|
/// - **[`Ref`].**
|
||||||
/// Similar to change detection filters but it is used as a query fetch parameter.
|
/// 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.
|
/// It exposes methods to check for changes to the wrapped component.
|
||||||
|
/// - **[`Mut`].**
|
||||||
|
/// Mutable component access, with change detection data.
|
||||||
/// - **[`Has`].**
|
/// - **[`Has`].**
|
||||||
/// Returns a bool indicating whether the entity has the specified component.
|
/// Returns a bool indicating whether the entity has the specified component.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -11,10 +11,10 @@ pub use relationship_query::*;
|
|||||||
pub use relationship_source_collection::*;
|
pub use relationship_source_collection::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, HookContext, Mutable},
|
component::{Component, Mutable},
|
||||||
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
||||||
error::{ignore, CommandWithEntity, HandleError},
|
error::{ignore, CommandWithEntity, HandleError},
|
||||||
system::entity_command::{self},
|
lifecycle::HookContext,
|
||||||
world::{DeferredWorld, EntityWorldMut},
|
world::{DeferredWorld, EntityWorldMut},
|
||||||
};
|
};
|
||||||
use log::warn;
|
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.
|
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||||
// note: think of this as "on_drop"
|
// 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 (entities, mut commands) = world.entities_and_commands();
|
||||||
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
||||||
for source_entity in relationship_target.iter() {
|
for source_entity in relationship_target.iter() {
|
||||||
if entities.get(source_entity).is_ok() {
|
commands
|
||||||
commands.queue(
|
.entity(source_entity)
|
||||||
entity_command::remove::<Self::Relationship>()
|
.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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
|
/// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
|
||||||
/// that entity is despawned.
|
/// that entity is despawned.
|
||||||
// note: think of this as "on_drop"
|
// 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 (entities, mut commands) = world.entities_and_commands();
|
||||||
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
|
||||||
for source_entity in relationship_target.iter() {
|
for source_entity in relationship_target.iter() {
|
||||||
if entities.get(source_entity).is_ok() {
|
commands.entity(source_entity).despawn();
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
entity::{hash_set::EntityHashSet, Entity},
|
entity::{hash_set::EntityHashSet, Entity},
|
||||||
|
prelude::Children,
|
||||||
relationship::{
|
relationship::{
|
||||||
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
||||||
},
|
},
|
||||||
@ -302,6 +303,15 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
self
|
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,
|
/// 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.
|
/// 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,
|
/// 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.
|
/// 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.
|
/// A system that determines if one or more scheduled systems should run.
|
||||||
///
|
///
|
||||||
/// Implemented for functions and closures that convert into [`System<Out=bool>`](System)
|
/// `SystemCondition` is sealed and implemented for functions and closures with
|
||||||
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
|
/// [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
|
/// # Marker type parameter
|
||||||
///
|
///
|
||||||
@ -31,7 +41,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # 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::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
/// fn every_other_time() -> impl SystemCondition<()> {
|
/// 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);
|
/// # 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::*;
|
/// # use bevy_ecs::prelude::*;
|
||||||
@ -71,8 +81,30 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
|
|||||||
/// # world.insert_resource(DidRun(false));
|
/// # world.insert_resource(DidRun(false));
|
||||||
/// # app.run(&mut world);
|
/// # app.run(&mut world);
|
||||||
/// # assert!(world.resource::<DidRun>().0);
|
/// # 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`
|
/// Returns a new run condition that only returns `true`
|
||||||
/// if both this one and the passed `and` return `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
|
impl<Marker, In: SystemInput, Out, F> SystemCondition<Marker, In, Out> for F where
|
||||||
F: sealed::SystemCondition<Marker, In>
|
F: sealed::SystemCondition<Marker, In, Out>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
mod sealed {
|
mod sealed {
|
||||||
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
|
use crate::{
|
||||||
|
error::BevyError,
|
||||||
|
system::{IntoSystem, ReadOnlySystem, SystemInput},
|
||||||
|
};
|
||||||
|
|
||||||
pub trait SystemCondition<Marker, In: SystemInput>:
|
pub trait SystemCondition<Marker, In: SystemInput, Out>:
|
||||||
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
|
IntoSystem<In, Out, Marker, System = Self::ReadOnlySystem>
|
||||||
{
|
{
|
||||||
// This associated type is necessary to let the compiler
|
// This associated type is necessary to let the compiler
|
||||||
// know that `Self::System` is `ReadOnlySystem`.
|
// 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
|
where
|
||||||
F: IntoSystem<In, bool, Marker>,
|
F: IntoSystem<In, bool, Marker>,
|
||||||
F::System: ReadOnlySystem,
|
F::System: ReadOnlySystem,
|
||||||
{
|
{
|
||||||
type ReadOnlySystem = F::System;
|
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::{
|
use crate::{
|
||||||
change_detection::DetectChanges,
|
change_detection::DetectChanges,
|
||||||
event::{Event, EventReader},
|
event::{Event, EventReader},
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
prelude::{Component, Query, With},
|
prelude::{Component, Query, With},
|
||||||
query::QueryFilter,
|
query::QueryFilter,
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{In, IntoSystem, Local, Res, System, SystemInput},
|
system::{In, IntoSystem, Local, Res, System, SystemInput},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,8 +14,8 @@ use crate::{
|
|||||||
system::{BoxedSystem, InfallibleSystemWrapper, IntoSystem, ScheduleSystem, System},
|
system::{BoxedSystem, InfallibleSystemWrapper, IntoSystem, ScheduleSystem, System},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn new_condition<M>(condition: impl SystemCondition<M>) -> BoxedCondition {
|
fn new_condition<M, Out>(condition: impl SystemCondition<M, (), Out>) -> BoxedCondition {
|
||||||
let condition_system = IntoSystem::into_system(condition);
|
let condition_system = condition.into_condition_system();
|
||||||
assert!(
|
assert!(
|
||||||
condition_system.is_send(),
|
condition_system.is_send(),
|
||||||
"SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.",
|
"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
|
/// 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.
|
/// 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)
|
self.into_configs().run_if(condition)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -535,7 +535,7 @@ impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleCo
|
|||||||
self
|
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.run_if_dyn(new_condition(condition));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,9 +18,9 @@ use crate::{
|
|||||||
component::{ComponentId, Tick},
|
component::{ComponentId, Tick},
|
||||||
error::{BevyError, ErrorContext, Result},
|
error::{BevyError, ErrorContext, Result},
|
||||||
prelude::{IntoSystemSet, SystemSet},
|
prelude::{IntoSystemSet, SystemSet},
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
||||||
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError},
|
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -162,35 +162,14 @@ impl System for ApplyDeferred {
|
|||||||
Cow::Borrowed("bevy_ecs::apply_deferred")
|
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> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
|
// This system accesses no components.
|
||||||
const { &FilteredAccessSet::new() }
|
const { &FilteredAccessSet::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
// Although this system itself does nothing on its own, the system
|
// non-send , exclusive , no deferred
|
||||||
// executor uses it to apply deferred commands. Commands must be allowed
|
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
@ -203,6 +182,10 @@ impl System for ApplyDeferred {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
#[inline]
|
||||||
|
fn refresh_hotpatch(&mut self) {}
|
||||||
|
|
||||||
fn run(&mut self, _input: SystemIn<'_, Self>, _world: &mut World) -> Self::Out {
|
fn run(&mut self, _input: SystemIn<'_, Self>, _world: &mut World) -> Self::Out {
|
||||||
// This system does nothing on its own. The executor will apply deferred
|
// This system does nothing on its own. The executor will apply deferred
|
||||||
// commands from other systems instead of running this system.
|
// commands from other systems instead of running this system.
|
||||||
|
|||||||
@ -19,6 +19,8 @@ use crate::{
|
|||||||
system::ScheduleSystem,
|
system::ScheduleSystem,
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
use crate::{event::Events, HotPatched};
|
||||||
|
|
||||||
use super::__rust_begin_short_backtrace;
|
use super::__rust_begin_short_backtrace;
|
||||||
|
|
||||||
@ -443,6 +445,14 @@ impl ExecutorState {
|
|||||||
return;
|
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`
|
// can't borrow since loop mutably borrows `self`
|
||||||
let mut ready_systems = core::mem::take(&mut self.ready_systems_copy);
|
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.
|
// Therefore, no other reference to this system exists and there is no aliasing.
|
||||||
let system = unsafe { &mut *context.environment.systems[system_index].get() };
|
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) {
|
if !self.can_run(system_index, conditions) {
|
||||||
// NOTE: exclusive systems with ambiguities are susceptible to
|
// NOTE: exclusive systems with ambiguities are susceptible to
|
||||||
// being significantly displaced here (compared to single-threaded order)
|
// being significantly displaced here (compared to single-threaded order)
|
||||||
|
|||||||
@ -16,6 +16,8 @@ use crate::{
|
|||||||
},
|
},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
use crate::{event::Events, HotPatched};
|
||||||
|
|
||||||
use super::__rust_begin_short_backtrace;
|
use super::__rust_begin_short_backtrace;
|
||||||
|
|
||||||
@ -60,6 +62,12 @@ impl SystemExecutor for SimpleExecutor {
|
|||||||
self.completed_systems |= skipped_systems;
|
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() {
|
for system_index in 0..schedule.systems.len() {
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let name = schedule.systems[system_index].name();
|
let name = schedule.systems[system_index].name();
|
||||||
@ -120,6 +128,11 @@ impl SystemExecutor for SimpleExecutor {
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
should_run_span.exit();
|
should_run_span.exit();
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
if should_update_hotpatch {
|
||||||
|
system.refresh_hotpatch();
|
||||||
|
}
|
||||||
|
|
||||||
// system has either been skipped or will run
|
// system has either been skipped or will run
|
||||||
self.completed_systems.insert(system_index);
|
self.completed_systems.insert(system_index);
|
||||||
|
|
||||||
@ -186,6 +199,12 @@ fn evaluate_and_fold_conditions(
|
|||||||
world: &mut World,
|
world: &mut World,
|
||||||
error_handler: ErrorHandler,
|
error_handler: ErrorHandler,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
let should_update_hotpatch = !world
|
||||||
|
.get_resource::<Events<HotPatched>>()
|
||||||
|
.map(Events::is_empty)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::unnecessary_fold,
|
clippy::unnecessary_fold,
|
||||||
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
if should_update_hotpatch {
|
||||||
|
condition.refresh_hotpatch();
|
||||||
|
}
|
||||||
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
||||||
})
|
})
|
||||||
.fold(true, |acc, res| acc && res)
|
.fold(true, |acc, res| acc && res)
|
||||||
|
|||||||
@ -12,6 +12,8 @@ use crate::{
|
|||||||
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
|
schedule::{is_apply_deferred, BoxedCondition, ExecutorKind, SystemExecutor, SystemSchedule},
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
use crate::{event::Events, HotPatched};
|
||||||
|
|
||||||
use super::__rust_begin_short_backtrace;
|
use super::__rust_begin_short_backtrace;
|
||||||
|
|
||||||
@ -60,6 +62,12 @@ impl SystemExecutor for SingleThreadedExecutor {
|
|||||||
self.completed_systems |= skipped_systems;
|
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() {
|
for system_index in 0..schedule.systems.len() {
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let name = schedule.systems[system_index].name();
|
let name = schedule.systems[system_index].name();
|
||||||
@ -121,6 +129,11 @@ impl SystemExecutor for SingleThreadedExecutor {
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
should_run_span.exit();
|
should_run_span.exit();
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
if should_update_hotpatch {
|
||||||
|
system.refresh_hotpatch();
|
||||||
|
}
|
||||||
|
|
||||||
// system has either been skipped or will run
|
// system has either been skipped or will run
|
||||||
self.completed_systems.insert(system_index);
|
self.completed_systems.insert(system_index);
|
||||||
|
|
||||||
@ -204,6 +217,12 @@ fn evaluate_and_fold_conditions(
|
|||||||
world: &mut World,
|
world: &mut World,
|
||||||
error_handler: ErrorHandler,
|
error_handler: ErrorHandler,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
let should_update_hotpatch = !world
|
||||||
|
.get_resource::<Events<HotPatched>>()
|
||||||
|
.map(Events::is_empty)
|
||||||
|
.unwrap_or(true);
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
clippy::unnecessary_fold,
|
clippy::unnecessary_fold,
|
||||||
reason = "Short-circuiting here would prevent conditions from mutating their own state as needed."
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
if should_update_hotpatch {
|
||||||
|
condition.refresh_hotpatch();
|
||||||
|
}
|
||||||
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
__rust_begin_short_backtrace::readonly_run(&mut **condition, world)
|
||||||
})
|
})
|
||||||
.fold(true, |acc, res| acc && res)
|
.fold(true, |acc, res| acc && res)
|
||||||
|
|||||||
@ -29,6 +29,7 @@ mod tests {
|
|||||||
use alloc::{string::ToString, vec, vec::Vec};
|
use alloc::{string::ToString, vec, vec::Vec};
|
||||||
use core::sync::atomic::{AtomicU32, Ordering};
|
use core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
|
use crate::error::BevyError;
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
prelude::World,
|
prelude::World,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
@ -49,10 +50,10 @@ mod tests {
|
|||||||
struct SystemOrder(Vec<u32>);
|
struct SystemOrder(Vec<u32>);
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
struct RunConditionBool(pub bool);
|
struct RunConditionBool(bool);
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
struct Counter(pub AtomicU32);
|
struct Counter(AtomicU32);
|
||||||
|
|
||||||
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
|
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
|
||||||
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
|
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
|
||||||
@ -252,12 +253,13 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod conditions {
|
mod conditions {
|
||||||
|
|
||||||
use crate::change_detection::DetectChanges;
|
use crate::change_detection::DetectChanges;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn system_with_condition() {
|
fn system_with_condition_bool() {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
let mut schedule = Schedule::default();
|
let mut schedule = Schedule::default();
|
||||||
|
|
||||||
@ -276,6 +278,47 @@ mod tests {
|
|||||||
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
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]
|
#[test]
|
||||||
fn systems_with_distributive_condition() {
|
fn systems_with_distributive_condition() {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -874,7 +917,6 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "Known failing but fix is non-trivial: https://github.com/bevyengine/bevy/issues/4381"]
|
|
||||||
fn filtered_components() {
|
fn filtered_components() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.spawn(A);
|
world.spawn(A);
|
||||||
|
|||||||
@ -166,7 +166,7 @@ impl Schedules {
|
|||||||
writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
|
writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("{}", message);
|
info!("{message}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
|
/// 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
|
/// Iterates the change ticks of all systems in the schedule and clamps any older than
|
||||||
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
|
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
|
||||||
/// This prevents overflow and thus prevents false positives.
|
/// 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 {
|
for system in &mut self.executable.systems {
|
||||||
if !is_apply_deferred(system) {
|
if !is_apply_deferred(system) {
|
||||||
system.check_change_tick(change_tick);
|
system.check_change_tick(change_tick);
|
||||||
@ -1418,26 +1418,24 @@ impl ScheduleGraph {
|
|||||||
if system_a.is_exclusive() || system_b.is_exclusive() {
|
if system_a.is_exclusive() || system_b.is_exclusive() {
|
||||||
conflicting_systems.push((a, b, Vec::new()));
|
conflicting_systems.push((a, b, Vec::new()));
|
||||||
} else {
|
} else {
|
||||||
let access_a = system_a.component_access();
|
let access_a = system_a.component_access_set();
|
||||||
let access_b = system_b.component_access();
|
let access_b = system_b.component_access_set();
|
||||||
if !access_a.is_compatible(access_b) {
|
match access_a.get_conflicts(access_b) {
|
||||||
match access_a.get_conflicts(access_b) {
|
AccessConflicts::Individual(conflicts) => {
|
||||||
AccessConflicts::Individual(conflicts) => {
|
let conflicts: Vec<_> = conflicts
|
||||||
let conflicts: Vec<_> = conflicts
|
.ones()
|
||||||
.ones()
|
.map(ComponentId::get_sparse_set_index)
|
||||||
.map(ComponentId::get_sparse_set_index)
|
.filter(|id| !ignored_ambiguities.contains(id))
|
||||||
.filter(|id| !ignored_ambiguities.contains(id))
|
.collect();
|
||||||
.collect();
|
if !conflicts.is_empty() {
|
||||||
if !conflicts.is_empty() {
|
conflicting_systems.push((a, b, conflicts));
|
||||||
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()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 {
|
match self.settings.hierarchy_detection {
|
||||||
LogLevel::Ignore => unreachable!(),
|
LogLevel::Ignore => unreachable!(),
|
||||||
LogLevel::Warn => {
|
LogLevel::Warn => {
|
||||||
error!(
|
error!("Schedule {schedule_label:?} has redundant edges:\n {message}");
|
||||||
"Schedule {schedule_label:?} has redundant edges:\n {}",
|
|
||||||
message
|
|
||||||
);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
|
LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
|
||||||
@ -1912,7 +1907,7 @@ impl ScheduleGraph {
|
|||||||
match self.settings.ambiguity_detection {
|
match self.settings.ambiguity_detection {
|
||||||
LogLevel::Ignore => Ok(()),
|
LogLevel::Ignore => Ok(()),
|
||||||
LogLevel::Warn => {
|
LogLevel::Warn => {
|
||||||
warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
|
warn!("Schedule {schedule_label:?} has ambiguities.\n{message}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
|
LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
|
||||||
|
|||||||
@ -60,7 +60,93 @@ define_label!(
|
|||||||
);
|
);
|
||||||
|
|
||||||
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(
|
#[diagnostic::on_unimplemented(
|
||||||
note = "consider annotating `{Self}` with `#[derive(SystemSet)]`"
|
note = "consider annotating `{Self}` with `#[derive(SystemSet)]`"
|
||||||
)]
|
)]
|
||||||
|
|||||||
@ -475,9 +475,8 @@ impl Stepping {
|
|||||||
Some(state) => state.clear_behaviors(),
|
Some(state) => state.clear_behaviors(),
|
||||||
None => {
|
None => {
|
||||||
warn!(
|
warn!(
|
||||||
"stepping is not enabled for schedule {:?}; \
|
"stepping is not enabled for schedule {label:?}; \
|
||||||
use `.add_stepping({:?})` to enable stepping",
|
use `.add_stepping({label:?})` to enable stepping"
|
||||||
label, label
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -486,9 +485,8 @@ impl Stepping {
|
|||||||
Some(state) => state.set_behavior(system, behavior),
|
Some(state) => state.set_behavior(system, behavior),
|
||||||
None => {
|
None => {
|
||||||
warn!(
|
warn!(
|
||||||
"stepping is not enabled for schedule {:?}; \
|
"stepping is not enabled for schedule {label:?}; \
|
||||||
use `.add_stepping({:?})` to enable stepping",
|
use `.add_stepping({label:?})` to enable stepping"
|
||||||
label, label
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -498,9 +496,8 @@ impl Stepping {
|
|||||||
Some(state) => state.clear_behavior(system),
|
Some(state) => state.clear_behavior(system),
|
||||||
None => {
|
None => {
|
||||||
warn!(
|
warn!(
|
||||||
"stepping is not enabled for schedule {:?}; \
|
"stepping is not enabled for schedule {label:?}; \
|
||||||
use `.add_stepping({:?})` to enable stepping",
|
use `.add_stepping({label:?})` to enable stepping"
|
||||||
label, label
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -127,26 +127,15 @@ where
|
|||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_access(&self) -> &crate::query::Access<crate::component::ComponentId> {
|
|
||||||
self.system.component_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn component_access_set(
|
fn component_access_set(
|
||||||
&self,
|
&self,
|
||||||
) -> &crate::query::FilteredAccessSet<crate::component::ComponentId> {
|
) -> &crate::query::FilteredAccessSet<crate::component::ComponentId> {
|
||||||
self.system.component_access_set()
|
self.system.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.system.is_send()
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
}
|
self.system.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.system.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -161,6 +150,12 @@ where
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
#[inline]
|
||||||
|
fn refresh_hotpatch(&mut self) {
|
||||||
|
self.system.refresh_hotpatch();
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn apply_deferred(&mut self, world: &mut crate::prelude::World) {
|
fn apply_deferred(&mut self, world: &mut crate::prelude::World) {
|
||||||
self.system.apply_deferred(world);
|
self.system.apply_deferred(world);
|
||||||
|
|||||||
@ -604,7 +604,7 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesBuilder)>
|
|||||||
if !conflicts.is_empty() {
|
if !conflicts.is_empty() {
|
||||||
let accesses = conflicts.format_conflict_list(world);
|
let accesses = conflicts.format_conflict_list(world);
|
||||||
let system_name = &meta.name;
|
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() {
|
if access.has_read_all_resources() {
|
||||||
@ -663,7 +663,7 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
|
|||||||
if !conflicts.is_empty() {
|
if !conflicts.is_empty() {
|
||||||
let accesses = conflicts.format_conflict_list(world);
|
let accesses = conflicts.format_conflict_list(world);
|
||||||
let system_name = &meta.name;
|
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() {
|
if access.has_read_all_resources() {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use core::marker::PhantomData;
|
|||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentId, Tick},
|
component::{ComponentId, Tick},
|
||||||
prelude::World,
|
prelude::World,
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
schedule::InternedSystemSet,
|
schedule::InternedSystemSet,
|
||||||
system::{input::SystemInput, SystemIn, SystemParamValidationError},
|
system::{input::SystemInput, SystemIn, SystemParamValidationError},
|
||||||
world::unsafe_world_cell::UnsafeWorldCell,
|
world::unsafe_world_cell::UnsafeWorldCell,
|
||||||
@ -144,24 +144,13 @@ where
|
|||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.component_access_set.combined_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
&self.component_access_set
|
&self.component_access_set
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.a.is_send() && self.b.is_send()
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
}
|
self.a.flags() | self.b.flags()
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
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]
|
#[inline]
|
||||||
fn apply_deferred(&mut self, world: &mut World) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
self.a.apply_deferred(world);
|
self.a.apply_deferred(world);
|
||||||
@ -363,24 +359,13 @@ where
|
|||||||
self.name.clone()
|
self.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.component_access_set.combined_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
&self.component_access_set
|
&self.component_access_set
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.a.is_send() && self.b.is_send()
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
}
|
self.a.flags() | self.b.flags()
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
@ -392,6 +377,13 @@ where
|
|||||||
self.b.run_unsafe(value, world)
|
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) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
self.a.apply_deferred(world);
|
self.a.apply_deferred(world);
|
||||||
self.b.apply_deferred(world);
|
self.b.apply_deferred(world);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentId, Tick},
|
component::{ComponentId, Tick},
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
schedule::{InternedSystemSet, SystemSet},
|
schedule::{InternedSystemSet, SystemSet},
|
||||||
system::{
|
system::{
|
||||||
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem,
|
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem,
|
||||||
@ -13,7 +13,7 @@ use alloc::{borrow::Cow, vec, vec::Vec};
|
|||||||
use core::marker::PhantomData;
|
use core::marker::PhantomData;
|
||||||
use variadics_please::all_tuples;
|
use variadics_please::all_tuples;
|
||||||
|
|
||||||
use super::SystemParamValidationError;
|
use super::{SystemParamValidationError, SystemStateFlags};
|
||||||
|
|
||||||
/// A function system that runs with exclusive [`World`] access.
|
/// A function system that runs with exclusive [`World`] access.
|
||||||
///
|
///
|
||||||
@ -26,6 +26,8 @@ where
|
|||||||
F: ExclusiveSystemParamFunction<Marker>,
|
F: ExclusiveSystemParamFunction<Marker>,
|
||||||
{
|
{
|
||||||
func: F,
|
func: F,
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
current_ptr: subsecond::HotFnPtr,
|
||||||
param_state: Option<<F::Param as ExclusiveSystemParam>::State>,
|
param_state: Option<<F::Param as ExclusiveSystemParam>::State>,
|
||||||
system_meta: SystemMeta,
|
system_meta: SystemMeta,
|
||||||
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
|
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
|
||||||
@ -58,6 +60,11 @@ where
|
|||||||
fn into_system(func: Self) -> Self::System {
|
fn into_system(func: Self) -> Self::System {
|
||||||
ExclusiveFunctionSystem {
|
ExclusiveFunctionSystem {
|
||||||
func,
|
func,
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
current_ptr: subsecond::HotFn::current(
|
||||||
|
<F as ExclusiveSystemParamFunction<Marker>>::run,
|
||||||
|
)
|
||||||
|
.ptr_address(),
|
||||||
param_state: None,
|
param_state: None,
|
||||||
system_meta: SystemMeta::new::<F>(),
|
system_meta: SystemMeta::new::<F>(),
|
||||||
marker: PhantomData,
|
marker: PhantomData,
|
||||||
@ -80,33 +87,18 @@ where
|
|||||||
self.system_meta.name.clone()
|
self.system_meta.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.system_meta.component_access_set.combined_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
&self.system_meta.component_access_set
|
&self.system_meta.component_access_set
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
// exclusive systems should have access to non-send resources
|
// non-send , exclusive , no deferred
|
||||||
// the executor runs exclusive systems on the main thread, so this
|
// the executor runs exclusive systems on the main thread, so this
|
||||||
// field reflects that constraint
|
// 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
|
// exclusive systems have no deferred system params
|
||||||
false
|
SystemStateFlags::NON_SEND | SystemStateFlags::EXCLUSIVE
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -125,6 +117,20 @@ where
|
|||||||
self.param_state.as_mut().expect(PARAM_MESSAGE),
|
self.param_state.as_mut().expect(PARAM_MESSAGE),
|
||||||
&self.system_meta,
|
&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);
|
let out = self.func.run(world, input, params);
|
||||||
|
|
||||||
world.flush();
|
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]
|
#[inline]
|
||||||
fn apply_deferred(&mut self, _world: &mut World) {
|
fn apply_deferred(&mut self, _world: &mut World) {
|
||||||
// "pure" exclusive systems do not have any buffers to apply.
|
// "pure" exclusive systems do not have any buffers to apply.
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentId, Tick},
|
component::{ComponentId, Tick},
|
||||||
prelude::FromWorld,
|
prelude::FromWorld,
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
schedule::{InternedSystemSet, SystemSet},
|
schedule::{InternedSystemSet, SystemSet},
|
||||||
system::{
|
system::{
|
||||||
check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam,
|
check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam,
|
||||||
@ -17,7 +17,9 @@ use variadics_please::all_tuples;
|
|||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
use tracing::{info_span, Span};
|
use tracing::{info_span, Span};
|
||||||
|
|
||||||
use super::{IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError};
|
use super::{
|
||||||
|
IntoSystem, ReadOnlySystem, SystemParamBuilder, SystemParamValidationError, SystemStateFlags,
|
||||||
|
};
|
||||||
|
|
||||||
/// The metadata of a [`System`].
|
/// The metadata of a [`System`].
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -29,8 +31,7 @@ pub struct SystemMeta {
|
|||||||
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
|
pub(crate) component_access_set: FilteredAccessSet<ComponentId>,
|
||||||
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
|
// NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent
|
||||||
// SystemParams from overriding each other
|
// SystemParams from overriding each other
|
||||||
is_send: bool,
|
flags: SystemStateFlags,
|
||||||
has_deferred: bool,
|
|
||||||
pub(crate) last_run: Tick,
|
pub(crate) last_run: Tick,
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
pub(crate) system_span: Span,
|
pub(crate) system_span: Span,
|
||||||
@ -44,8 +45,7 @@ impl SystemMeta {
|
|||||||
Self {
|
Self {
|
||||||
name: name.into(),
|
name: name.into(),
|
||||||
component_access_set: FilteredAccessSet::default(),
|
component_access_set: FilteredAccessSet::default(),
|
||||||
is_send: true,
|
flags: SystemStateFlags::empty(),
|
||||||
has_deferred: false,
|
|
||||||
last_run: Tick::new(0),
|
last_run: Tick::new(0),
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
system_span: info_span!("system", name = name),
|
system_span: info_span!("system", name = name),
|
||||||
@ -78,7 +78,7 @@ impl SystemMeta {
|
|||||||
/// Returns true if the system is [`Send`].
|
/// Returns true if the system is [`Send`].
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_send(&self) -> bool {
|
pub fn is_send(&self) -> bool {
|
||||||
self.is_send
|
!self.flags.intersects(SystemStateFlags::NON_SEND)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the system to be not [`Send`].
|
/// Sets the system to be not [`Send`].
|
||||||
@ -86,20 +86,20 @@ impl SystemMeta {
|
|||||||
/// This is irreversible.
|
/// This is irreversible.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_non_send(&mut self) {
|
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
|
/// Returns true if the system has deferred [`SystemParam`]'s
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn has_deferred(&self) -> bool {
|
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`)
|
/// Marks the system as having deferred buffers like [`Commands`](`super::Commands`)
|
||||||
/// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically.
|
/// This lets the scheduler insert [`ApplyDeferred`](`crate::prelude::ApplyDeferred`) systems automatically.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_has_deferred(&mut self) {
|
pub fn set_has_deferred(&mut self) {
|
||||||
self.has_deferred = true;
|
self.flags |= SystemStateFlags::DEFERRED;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the [`FilteredAccessSet`] for [`ComponentId`].
|
/// Returns a reference to the [`FilteredAccessSet`] for [`ComponentId`].
|
||||||
@ -306,6 +306,9 @@ impl<Param: SystemParam> SystemState<Param> {
|
|||||||
) -> FunctionSystem<Marker, F> {
|
) -> FunctionSystem<Marker, F> {
|
||||||
FunctionSystem {
|
FunctionSystem {
|
||||||
func,
|
func,
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
|
||||||
|
.ptr_address(),
|
||||||
state: Some(FunctionSystemState {
|
state: Some(FunctionSystemState {
|
||||||
param: self.param_state,
|
param: self.param_state,
|
||||||
world_id: self.world_id,
|
world_id: self.world_id,
|
||||||
@ -519,6 +522,8 @@ where
|
|||||||
F: SystemParamFunction<Marker>,
|
F: SystemParamFunction<Marker>,
|
||||||
{
|
{
|
||||||
func: F,
|
func: F,
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
current_ptr: subsecond::HotFnPtr,
|
||||||
state: Option<FunctionSystemState<F::Param>>,
|
state: Option<FunctionSystemState<F::Param>>,
|
||||||
system_meta: SystemMeta,
|
system_meta: SystemMeta,
|
||||||
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
|
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
|
||||||
@ -558,6 +563,9 @@ where
|
|||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
func: self.func.clone(),
|
func: self.func.clone(),
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
|
||||||
|
.ptr_address(),
|
||||||
state: None,
|
state: None,
|
||||||
system_meta: SystemMeta::new::<F>(),
|
system_meta: SystemMeta::new::<F>(),
|
||||||
marker: PhantomData,
|
marker: PhantomData,
|
||||||
@ -578,6 +586,9 @@ where
|
|||||||
fn into_system(func: Self) -> Self::System {
|
fn into_system(func: Self) -> Self::System {
|
||||||
FunctionSystem {
|
FunctionSystem {
|
||||||
func,
|
func,
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
current_ptr: subsecond::HotFn::current(<F as SystemParamFunction<Marker>>::run)
|
||||||
|
.ptr_address(),
|
||||||
state: None,
|
state: None,
|
||||||
system_meta: SystemMeta::new::<F>(),
|
system_meta: SystemMeta::new::<F>(),
|
||||||
marker: PhantomData,
|
marker: PhantomData,
|
||||||
@ -609,29 +620,14 @@ where
|
|||||||
self.system_meta.name.clone()
|
self.system_meta.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.system_meta.component_access_set.combined_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
&self.system_meta.component_access_set
|
&self.system_meta.component_access_set
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
self.system_meta.is_send
|
self.system_meta.flags
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system_meta.has_deferred
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -653,11 +649,35 @@ where
|
|||||||
// will ensure that there are no data access conflicts.
|
// will ensure that there are no data access conflicts.
|
||||||
let params =
|
let params =
|
||||||
unsafe { F::Param::get_param(&mut state.param, &self.system_meta, world, change_tick) };
|
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);
|
let out = self.func.run(input, params);
|
||||||
|
|
||||||
self.system_meta.last_run = change_tick;
|
self.system_meta.last_run = change_tick;
|
||||||
out
|
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]
|
#[inline]
|
||||||
fn apply_deferred(&mut self, world: &mut World) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
let param_state = &mut self.state.as_mut().expect(Self::ERROR_UNINITIALIZED).param;
|
let param_state = &mut self.state.as_mut().expect(Self::ERROR_UNINITIALIZED).param;
|
||||||
|
|||||||
@ -97,7 +97,7 @@
|
|||||||
//! - [`EventWriter`](crate::event::EventWriter)
|
//! - [`EventWriter`](crate::event::EventWriter)
|
||||||
//! - [`NonSend`] and `Option<NonSend>`
|
//! - [`NonSend`] and `Option<NonSend>`
|
||||||
//! - [`NonSendMut`] and `Option<NonSendMut>`
|
//! - [`NonSendMut`] and `Option<NonSendMut>`
|
||||||
//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents)
|
//! - [`RemovedComponents`](crate::lifecycle::RemovedComponents)
|
||||||
//! - [`SystemName`]
|
//! - [`SystemName`]
|
||||||
//! - [`SystemChangeTick`]
|
//! - [`SystemChangeTick`]
|
||||||
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
|
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
|
||||||
@ -408,10 +408,10 @@ mod tests {
|
|||||||
component::{Component, Components},
|
component::{Component, Components},
|
||||||
entity::{Entities, Entity},
|
entity::{Entities, Entity},
|
||||||
error::Result,
|
error::Result,
|
||||||
|
lifecycle::RemovedComponents,
|
||||||
name::Name,
|
name::Name,
|
||||||
prelude::{AnyOf, EntityRef, Trigger},
|
prelude::{AnyOf, EntityRef, OnAdd, Trigger},
|
||||||
query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without},
|
query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without},
|
||||||
removal_detection::RemovedComponents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{
|
schedule::{
|
||||||
common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule,
|
common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule,
|
||||||
@ -421,7 +421,7 @@ mod tests {
|
|||||||
Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res,
|
Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res,
|
||||||
ResMut, Single, StaticSystemParam, System, SystemState,
|
ResMut, Single, StaticSystemParam, System, SystemState,
|
||||||
},
|
},
|
||||||
world::{DeferredWorld, EntityMut, FromWorld, OnAdd, World},
|
world::{DeferredWorld, EntityMut, FromWorld, World},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::ScheduleSystem;
|
use super::ScheduleSystem;
|
||||||
@ -1166,7 +1166,9 @@ mod tests {
|
|||||||
x.initialize(&mut world);
|
x.initialize(&mut world);
|
||||||
y.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
|
let b_id = world
|
||||||
.components()
|
.components()
|
||||||
.get_resource_id(TypeId::of::<B>())
|
.get_resource_id(TypeId::of::<B>())
|
||||||
@ -1630,7 +1632,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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 assert_world_and_entity_mut_system_does_conflict_first() {
|
||||||
fn system(_query: &World, _q2: Query<EntityMut>) {}
|
fn system(_query: &World, _q2: Query<EntityMut>) {}
|
||||||
@ -1648,7 +1650,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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 assert_entity_ref_and_entity_mut_system_does_conflict() {
|
||||||
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
|
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
|
||||||
@ -1657,7 +1659,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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 assert_entity_mut_system_does_conflict() {
|
||||||
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
|
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
|
||||||
@ -1666,7 +1668,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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 assert_deferred_world_and_entity_ref_system_does_conflict_first() {
|
||||||
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}
|
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use crate::{
|
|||||||
error::Result,
|
error::Result,
|
||||||
never::Never,
|
never::Never,
|
||||||
prelude::{Bundle, Trigger},
|
prelude::{Bundle, Trigger},
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
schedule::{Fallible, Infallible},
|
schedule::{Fallible, Infallible},
|
||||||
system::{input::SystemIn, System},
|
system::{input::SystemIn, System},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
@ -116,29 +116,14 @@ where
|
|||||||
self.observer.name()
|
self.observer.name()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.observer.component_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
self.observer.component_access_set()
|
self.observer.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> super::SystemStateFlags {
|
||||||
self.observer.is_send()
|
self.observer.flags()
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.observer.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.observer.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -151,6 +136,12 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
#[inline]
|
||||||
|
fn refresh_hotpatch(&mut self) {
|
||||||
|
self.observer.refresh_hotpatch();
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn apply_deferred(&mut self, world: &mut World) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
self.observer.apply_deferred(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()
|
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>`.
|
/// A transmute is valid only if `NewD` has a subset of the read, write, and required access
|
||||||
/// This can be useful for passing the query to another function. Note that since
|
/// of the current query. A precise description of the access required by each parameter
|
||||||
/// filter terms are dropped, non-archetypal filters like [`Added`](crate::query::Added),
|
/// type is given in the table below, but typical uses are to:
|
||||||
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will not be
|
/// * Remove components, e.g. `Query<(&A, &B)>` to `Query<&A>`.
|
||||||
/// respected. To maintain or change filter terms see [`Self::transmute_lens_filtered`]
|
/// * 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
|
/// ## 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
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -2061,30 +2111,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
/// # schedule.run(&mut world);
|
/// # 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
|
/// ### Examples of valid transmutes
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```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>`.
|
/// // 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>)>>();
|
/// 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]
|
#[track_caller]
|
||||||
pub fn transmute_lens<NewD: QueryData>(&mut self) -> QueryLens<'_, NewD> {
|
pub fn transmute_lens<NewD: QueryData>(&mut self) -> QueryLens<'_, NewD> {
|
||||||
self.transmute_lens_filtered::<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.
|
/// 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>`.
|
/// See [`Self::transmute_lens`] for a description of allowed transmutes.
|
||||||
/// 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`]
|
|
||||||
///
|
///
|
||||||
/// ## Panics
|
/// ## 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
|
/// ## Example
|
||||||
///
|
///
|
||||||
@ -2221,22 +2240,6 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
|
|||||||
/// # schedule.run(&mut world);
|
/// # 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
|
/// # See also
|
||||||
///
|
///
|
||||||
/// - [`transmute_lens`](Self::transmute_lens) to convert to a lens using a mutable borrow of the [`Query`].
|
/// - [`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.
|
/// 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
|
/// 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)
|
/// 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),
|
/// 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.
|
/// 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.
|
/// 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
|
/// 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)
|
/// 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),
|
/// 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
|
/// [`Changed`](crate::query::Changed) and [`Spawned`](crate::query::Spawned) will only be respected if they
|
||||||
|
/// are in the type signature.
|
||||||
///
|
///
|
||||||
/// # See also
|
/// # 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{prelude::*, query::QueryEntityError};
|
use crate::{prelude::*, query::QueryEntityError};
|
||||||
|
|||||||
@ -3,12 +3,12 @@ use alloc::{borrow::Cow, vec::Vec};
|
|||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentId, Tick},
|
component::{ComponentId, Tick},
|
||||||
error::Result,
|
error::Result,
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
system::{input::SystemIn, BoxedSystem, System, SystemInput},
|
system::{input::SystemIn, BoxedSystem, System, SystemInput},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
|
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`]
|
/// 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);
|
pub struct InfallibleSystemWrapper<S: System<In = ()>>(S);
|
||||||
@ -33,29 +33,14 @@ impl<S: System<In = ()>> System for InfallibleSystemWrapper<S> {
|
|||||||
self.0.type_id()
|
self.0.type_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.0.component_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
self.0.component_access_set()
|
self.0.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn is_send(&self) -> bool {
|
fn flags(&self) -> SystemStateFlags {
|
||||||
self.0.is_send()
|
self.0.flags()
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.0.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.0.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -68,6 +53,12 @@ impl<S: System<In = ()>> System for InfallibleSystemWrapper<S> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hotpatching")]
|
||||||
|
#[inline]
|
||||||
|
fn refresh_hotpatch(&mut self) {
|
||||||
|
self.0.refresh_hotpatch();
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn apply_deferred(&mut self, world: &mut World) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
self.0.apply_deferred(world);
|
self.0.apply_deferred(world);
|
||||||
@ -158,24 +149,13 @@ where
|
|||||||
self.system.name()
|
self.system.name()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.system.component_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
self.system.component_access_set()
|
self.system.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.system.is_send()
|
fn flags(&self) -> SystemStateFlags {
|
||||||
}
|
self.system.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.system.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
@ -186,6 +166,12 @@ where
|
|||||||
self.system.run_unsafe(&mut self.value, world)
|
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) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
self.system.apply_deferred(world);
|
self.system.apply_deferred(world);
|
||||||
}
|
}
|
||||||
@ -261,24 +247,13 @@ where
|
|||||||
self.system.name()
|
self.system.name()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_access(&self) -> &Access<ComponentId> {
|
|
||||||
self.system.component_access()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||||
self.system.component_access_set()
|
self.system.component_access_set()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_send(&self) -> bool {
|
#[inline]
|
||||||
self.system.is_send()
|
fn flags(&self) -> SystemStateFlags {
|
||||||
}
|
self.system.flags()
|
||||||
|
|
||||||
fn is_exclusive(&self) -> bool {
|
|
||||||
self.system.is_exclusive()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn has_deferred(&self) -> bool {
|
|
||||||
self.system.has_deferred()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn run_unsafe(
|
unsafe fn run_unsafe(
|
||||||
@ -293,6 +268,12 @@ where
|
|||||||
self.system.run_unsafe(value, world)
|
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) {
|
fn apply_deferred(&mut self, world: &mut World) {
|
||||||
self.system.apply_deferred(world);
|
self.system.apply_deferred(world);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,14 @@
|
|||||||
clippy::module_inception,
|
clippy::module_inception,
|
||||||
reason = "This instance of module inception is being discussed; see #17353."
|
reason = "This instance of module inception is being discussed; see #17353."
|
||||||
)]
|
)]
|
||||||
|
use bitflags::bitflags;
|
||||||
use core::fmt::Debug;
|
use core::fmt::Debug;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentId, Tick},
|
component::{ComponentId, Tick},
|
||||||
query::{Access, FilteredAccessSet},
|
query::FilteredAccessSet,
|
||||||
schedule::InternedSystemSet,
|
schedule::InternedSystemSet,
|
||||||
system::{input::SystemInput, SystemIn},
|
system::{input::SystemInput, SystemIn},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||||
@ -19,6 +20,18 @@ use core::any::TypeId;
|
|||||||
|
|
||||||
use super::{IntoSystem, SystemParamValidationError};
|
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)
|
/// An ECS system that can be added to a [`Schedule`](crate::schedule::Schedule)
|
||||||
///
|
///
|
||||||
/// Systems are functions with all arguments implementing
|
/// Systems are functions with all arguments implementing
|
||||||
@ -44,20 +57,29 @@ pub trait System: Send + Sync + 'static {
|
|||||||
TypeId::of::<Self>()
|
TypeId::of::<Self>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the system's component [`Access`].
|
|
||||||
fn component_access(&self) -> &Access<ComponentId>;
|
|
||||||
|
|
||||||
/// Returns the system's component [`FilteredAccessSet`].
|
/// Returns the system's component [`FilteredAccessSet`].
|
||||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId>;
|
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId>;
|
||||||
|
|
||||||
|
/// Returns the [`SystemStateFlags`] of the system.
|
||||||
|
fn flags(&self) -> SystemStateFlags;
|
||||||
|
|
||||||
/// Returns true if the system is [`Send`].
|
/// 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.
|
/// 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.
|
/// 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
|
/// 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
|
/// 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)
|
unsafe fn run_unsafe(&mut self, input: SystemIn<'_, Self>, world: UnsafeWorldCell)
|
||||||
-> Self::Out;
|
-> 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.
|
/// Runs the system with the given input in the world.
|
||||||
///
|
///
|
||||||
/// For [read-only](ReadOnlySystem) systems, see [`run_readonly`], which can be called using `&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);
|
let result = world.run_system_once(system);
|
||||||
|
|
||||||
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
|
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());
|
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 mut world = World::new();
|
||||||
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
|
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
|
||||||
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
|
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
|
||||||
/// assert!(err.to_string().ends_with(expected));
|
/// assert!(err.to_string().contains(expected));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ## Builders
|
/// ## Builders
|
||||||
@ -380,7 +380,7 @@ fn assert_component_access_compatibility(
|
|||||||
if !accesses.is_empty() {
|
if !accesses.is_empty() {
|
||||||
accesses.push(' ');
|
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
|
// 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();
|
let combined_access = system_meta.component_access_set.combined_access();
|
||||||
assert!(
|
assert!(
|
||||||
!combined_access.has_resource_write(component_id),
|
!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>(),
|
core::any::type_name::<T>(),
|
||||||
system_meta.name,
|
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();
|
let combined_access = system_meta.component_access_set.combined_access();
|
||||||
if combined_access.has_resource_write(component_id) {
|
if combined_access.has_resource_write(component_id) {
|
||||||
panic!(
|
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);
|
core::any::type_name::<T>(), system_meta.name);
|
||||||
} else if combined_access.has_resource_read(component_id) {
|
} else if combined_access.has_resource_read(component_id) {
|
||||||
panic!(
|
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);
|
core::any::type_name::<T>(), system_meta.name);
|
||||||
}
|
}
|
||||||
system_meta
|
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();
|
let combined_access = system_meta.component_access_set.combined_access();
|
||||||
assert!(
|
assert!(
|
||||||
!combined_access.has_resource_write(component_id),
|
!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>(),
|
core::any::type_name::<T>(),
|
||||||
system_meta.name,
|
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();
|
let combined_access = system_meta.component_access_set.combined_access();
|
||||||
if combined_access.has_component_write(component_id) {
|
if combined_access.has_component_write(component_id) {
|
||||||
panic!(
|
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);
|
core::any::type_name::<T>(), system_meta.name);
|
||||||
} else if combined_access.has_component_read(component_id) {
|
} else if combined_access.has_component_read(component_id) {
|
||||||
panic!(
|
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);
|
core::any::type_name::<T>(), system_meta.name);
|
||||||
}
|
}
|
||||||
system_meta
|
system_meta
|
||||||
@ -2578,7 +2578,11 @@ impl Display for SystemParamValidationError {
|
|||||||
ShortName(&self.param),
|
ShortName(&self.param),
|
||||||
self.field,
|
self.field,
|
||||||
self.message
|
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,
|
result,
|
||||||
Err(RegisteredSystemError::InvalidParams { .. })
|
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());
|
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