Merge branch 'main' into reflect-auto-registration

This commit is contained in:
eugineerd 2025-06-21 12:43:54 +00:00
commit e383ab55e9
449 changed files with 13962 additions and 4700 deletions

View File

@ -95,7 +95,7 @@ jobs:
- name: CI job
# To run the tests one item at a time for troubleshooting, use
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
run: cargo miri test -p bevy_ecs
run: cargo miri test -p bevy_ecs --features bevy_utils/debug
env:
# -Zrandomize-layout makes sure we dont rely on the layout of anything that might change
RUSTFLAGS: -Zrandomize-layout
@ -247,7 +247,7 @@ jobs:
- name: Check wasm
run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort
env:
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory -D warnings"
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory"
markdownlint:
runs-on: ubuntu-latest
@ -293,7 +293,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.32.0
uses: crate-ci/typos@v1.33.1
- name: Typos info
if: failure()
run: |

View File

@ -74,7 +74,6 @@ allow_attributes_without_reason = "warn"
[workspace.lints.rust]
missing_docs = "warn"
mismatched_lifetime_syntaxes = "allow"
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
unsafe_code = "deny"
unsafe_op_in_unsafe_fn = "warn"
@ -136,6 +135,7 @@ default = [
"bevy_audio",
"bevy_color",
"bevy_core_pipeline",
"bevy_core_widgets",
"bevy_anti_aliasing",
"bevy_gilrs",
"bevy_gizmos",
@ -167,6 +167,7 @@ default = [
"vorbis",
"webgl2",
"x11",
"debug",
]
# Recommended defaults for no_std applications
@ -249,6 +250,15 @@ bevy_render = ["bevy_internal/bevy_render", "bevy_color"]
# Provides scene functionality
bevy_scene = ["bevy_internal/bevy_scene", "bevy_asset"]
# Provides raytraced lighting (experimental)
bevy_solari = [
"bevy_internal/bevy_solari",
"bevy_asset",
"bevy_core_pipeline",
"bevy_pbr",
"bevy_render",
]
# Provides sprite functionality
bevy_sprite = [
"bevy_internal/bevy_sprite",
@ -295,6 +305,9 @@ bevy_log = ["bevy_internal/bevy_log"]
# Enable input focus subsystem
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
# Headless widget collection for Bevy UI.
bevy_core_widgets = ["bevy_internal/bevy_core_widgets"]
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
@ -497,7 +510,10 @@ file_watcher = ["bevy_internal/file_watcher"]
embedded_watcher = ["bevy_internal/embedded_watcher"]
# Enable stepping-based debugging of Bevy systems
bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"]
bevy_debug_stepping = [
"bevy_internal/bevy_debug_stepping",
"bevy_internal/debug",
]
# Enables the meshlet renderer for dense high-poly scenes (experimental)
meshlet = ["bevy_internal/meshlet"]
@ -547,6 +563,9 @@ web = ["bevy_internal/web"]
# Enable hotpatching of Bevy systems
hotpatching = ["bevy_internal/hotpatching"]
# Enable collecting debug information about systems and components to help with diagnostics
debug = ["bevy_internal/debug"]
[dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
tracing = { version = "0.1", default-features = false, optional = true }
@ -558,7 +577,7 @@ bevy_dylib = { path = "crates/bevy_dylib", version = "0.16.0-dev", default-featu
[dev-dependencies]
rand = "0.8.0"
rand_chacha = "0.3.1"
ron = "0.8.0"
ron = "0.10"
flate2 = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.140"
@ -597,7 +616,7 @@ web-sys = { version = "0.3", features = ["Window"] }
[[example]]
name = "context_menu"
path = "examples/usages/context_menu.rs"
path = "examples/usage/context_menu.rs"
doc-scrape-examples = true
[package.metadata.example.context_menu]
@ -1266,6 +1285,18 @@ description = "Load a cubemap texture onto a cube like a skybox and cycle throug
category = "3D Rendering"
wasm = false
[[example]]
name = "solari"
path = "examples/3d/solari.rs"
doc-scrape-examples = true
required-features = ["bevy_solari"]
[package.metadata.example.solari]
name = "Solari"
description = "Demonstrates realtime dynamic global illumination rendering using Bevy Solari."
category = "3D Rendering"
wasm = false
[[example]]
name = "spherical_area_lights"
path = "examples/3d/spherical_area_lights.rs"
@ -2082,6 +2113,7 @@ wasm = false
name = "dynamic"
path = "examples/ecs/dynamic.rs"
doc-scrape-examples = true
required-features = ["debug"]
[package.metadata.example.dynamic]
name = "Dynamic ECS"
@ -3563,6 +3595,17 @@ description = "Illustrates how to use 9 Slicing for TextureAtlases in UI"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "ui_transform"
path = "examples/ui/ui_transform.rs"
doc-scrape-examples = true
[package.metadata.example.ui_transform]
name = "UI Transform"
description = "An example demonstrating how to translate, rotate and scale UI elements."
category = "UI (User Interface)"
wasm = true
[[example]]
name = "viewport_debug"
path = "examples/ui/viewport_debug.rs"
@ -4447,3 +4490,25 @@ name = "Hotpatching Systems"
description = "Demonstrates how to hotpatch systems"
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "core_widgets"
path = "examples/ui/core_widgets.rs"
doc-scrape-examples = true
[package.metadata.example.core_widgets]
name = "Core Widgets"
description = "Demonstrates use of core (headless) widgets in Bevy UI"
category = "UI (User Interface)"
wasm = true
[[example]]
name = "core_widgets_observers"
path = "examples/ui/core_widgets_observers.rs"
doc-scrape-examples = true
[package.metadata.example.core_widgets_observers]
name = "Core Widgets (w/Observers)"
description = "Demonstrates use of core (headless) widgets in Bevy UI, with Observers"
category = "UI (User Interface)"
wasm = true

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="svg2321"
version="1.1"
viewBox="0 0 63.304429 63.304432"
height="63.304432mm"
width="63.304428mm"
sodipodi:docname="bevy_solari.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
inkscape:zoom="0.27643554"
inkscape:cx="54.262198"
inkscape:cy="-311.10327"
inkscape:window-width="1440"
inkscape:window-height="788"
inkscape:window-x="-6"
inkscape:window-y="-6"
inkscape:window-maximized="1"
inkscape:current-layer="svg2321" />
<defs
id="defs2315" />
<metadata
id="metadata2318">
<rdf:RDF>
<cc:Work
rdf:about="" />
</rdf:RDF>
</metadata>
<g
id="g11"
style="display:inline"
transform="translate(-27.298342,-111.49082)">
<path
style="fill:#ff8904;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-opacity:1"
id="path10-7"
d="m 38.15031,109.44704 c 0.743942,3.07133 6.496307,7.00051 6.678099,10.15542 0.116231,2.01713 -5.225098,3.23914 -5.79502,5.17757 -0.891392,3.03182 2.125712,9.31077 0.705692,12.1339 -0.907907,1.80501 -6.144635,0.19263 -7.607418,1.5864 -2.287879,2.17994 -2.814466,9.12622 -5.455804,10.86111 -1.688772,1.10923 -5.417723,-2.9055 -7.381414,-2.42985 -3.071331,0.74394 -7.000511,6.49631 -10.1554234,6.6781 -2.0171309,0.11623 -3.2391354,-5.2251 -5.1775665,-5.79502 -3.03182154,-0.89139 -9.3107731,2.12571 -12.1339038,0.70569 -1.8050021,-0.9079 -0.1926248,-6.14463 -1.5863942,-7.60742 -2.1799381,-2.28787 -9.1262221,-2.81446 -10.8611151,-5.4558 -1.109224,-1.68877 2.9055,-5.41772 2.429851,-7.38141 -0.743942,-3.07133 -6.496306,-7.00051 -6.678099,-10.15543 -0.116231,-2.01713 5.225098,-3.23913 5.79502,-5.17756 0.891393,-3.03182 -2.125711,-9.31078 -0.705692,-12.13391 0.907907,-1.804999 6.144635,-0.19262 7.607418,-1.586391 2.2878791,-2.179939 2.8144662,-9.126222 5.4558046,-10.861115 1.6887711,-1.109225 5.4177222,2.905499 7.38141398,2.429851 3.07133072,-0.743942 7.00051032,-6.496307 10.15542342,-6.678099 2.01713,-0.116231 3.239135,5.225097 5.177566,5.79502 3.031822,0.891392 9.310773,-2.125712 12.133904,-0.705692 1.805002,0.907906 0.192625,6.144634 1.586394,7.607417 2.179938,2.28788 9.126222,2.814467 10.861115,5.455809 1.109224,1.68877 -2.905499,5.41772 -2.429851,7.38141 z"
transform="matrix(0.90823691,0,0,0.90823691,49.886257,35.27956)" />
<g
id="g3-6"
transform="translate(-517.96199,-278.01119)"
style="display:inline;opacity:1;fill:#ffd230;fill-opacity:1;stroke-width:0.755906;stroke-miterlimit:4;stroke-dasharray:none">
<g
transform="matrix(-0.40426719,-0.17438247,-0.17438247,0.40426719,678.77611,389.84765)"
style="fill:#ffd230;fill-opacity:1"
id="g2-1">
<path
id="path1-4"
style="fill:#ffd230;fill-opacity:1;stroke:none;stroke-width:0.559814px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 2246.0312,2340.1914 v 0 c -0.016,3e-4 -0.031,0 -0.047,0 -0.9804,3.0675 -1.7386,6.3997 -1.8828,10.1953 -0.2712,7.1263 0.453,11.4639 -0.3672,16.0801 -0.8202,4.6163 -3.2453,9.161 -9.4141,16.2871 -7.3424,8.482 -18.9789,15.0453 -32.4199,17.2637 -2.5015,1.5971 -5.1421,3.0609 -7.9199,4.3633 10.4618,3.9385 21.4025,4.1531 30.0761,1.3066 15.2793,-5.0141 14.0962,-8.6155 20.9434,-19.1074 2.1569,-3.3051 4.6474,-5.8282 7.1484,-7.9004 7.1248,3.1068 14.1431,5.1015 18.5157,4.6074 2.351,-5.4505 -0.057,-11.7712 -4.0586,-17.7461 3.2821,-10.196 -1.6986,-20.4059 -12.7305,-24.0156 -2.8775,-0.9415 -5.4633,-1.3844 -7.8438,-1.3379 z m 8.2754,14.9707 a 4.1668789,4.2454995 48.679502 0 1 3.1973,1.3965 4.1668789,4.2454995 48.679502 0 1 -0.4375,5.9336 4.1668789,4.2454995 48.679502 0 1 -5.9394,-0.3262 4.1668789,4.2454995 48.679502 0 1 0.4375,-5.9336 4.1668789,4.2454995 48.679502 0 1 2.7421,-1.0703 z m -68.375,45.3789 c 0.1273,0.075 0.2572,0.1408 0.3848,0.2149 0.131,-0.049 0.2642,-0.1009 0.3945,-0.1504 -0.2598,-0.023 -0.5188,-0.039 -0.7793,-0.064 z"
transform="matrix(-0.55180403,-0.23802315,-0.23802315,0.55180403,1946.7322,-620.612)" />
</g>
</g>
<g
id="g5-2"
transform="matrix(-0.45399624,0.36689705,0.36689705,0.45399624,73.527335,10.816805)"
style="display:inline;opacity:1;fill:#fef3c6;fill-opacity:1;stroke:none;stroke-width:0.755906;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
<g
id="g4-3"
transform="matrix(-0.35254083,0.28490586,0.28490586,0.35254083,477.11004,-1021.7666)"
style="opacity:1;fill:#fef3c6;fill-opacity:1">
<path
id="path2-2"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#fef3c6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.02362;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 2191.1465,2276.7832 c -5.9729,-0.035 -12.0979,2.348 -17.3613,7.459 -6.9129,6.7127 -9.0602,12.7555 -7.8477,20.2949 l 0.332,2.0684 -2.0664,-0.336 c -15.1877,-2.4609 -33.9847,-1.2178 -55.3711,7.4336 6.2868,2.6948 17.8259,7.1926 30.6309,13.3418 l 4.0605,1.9512 -4.414,0.8945 c -16.9087,3.4274 -36.9729,13.3275 -55.2989,34.9336 8.1981,-0.6372 24.9531,-2.6089 42.4278,-2.582 9.7138,0.015 19.2869,0.687 27.0859,2.709 7.7991,2.022 14.8874,6.6498 15.8861,10.0406 0.9987,3.3908 0.432,5.1761 -0.5519,7.8285 -0.9839,2.6524 -4.0098,6.6817 -8.1953,9.3418 -4.1855,2.6601 -9.4961,4.9849 -15.0137,6.9609 -11.0352,3.9521 -22.7798,6.4773 -27.9648,6.959 -1.1021,0.1024 -1.5421,0.4983 -1.9668,1.2696 -0.4247,0.7712 -0.659,1.9824 -0.6934,3.25 -0.046,1.6926 0.217,2.576 0.6949,3.246 0.4779,0.67 1.2243,0.9381 1.9934,0.9902 32.5822,2.2052 56.9441,-5.9907 74.6379,-13.0116 20.3508,-9.3311 33.2134,-27.7577 36.0058,-44.3477 1.7499,-10.395 1.3746,-15.4894 -0.3124,-19.8281 -1.6873,-4.3387 -4.9223,-8.1914 -9.0254,-15.5488 -2.6368,-4.7281 -4.1077,-9.367 -5.0196,-13.6875 l -0.1933,-0.9102 0.7265,-0.582 c 7.5403,-6.0446 13.6809,-12.6444 15.9102,-17.4492 -4.5742,-4.8648 -12.4787,-5.893 -21.3223,-4.9473 l -0.7265,0.076 -0.5118,-0.5215 c -4.7125,-4.8006 -10.5615,-7.2614 -16.5351,-7.2969 z m 2.6484,11.2324 c 2.593,-0.041 4.8808,1.7566 5.502,4.3223 0.7307,3.0216 -1.0812,6.0525 -4.0469,6.7695 -2.9656,0.7176 -5.9625,-1.1502 -6.6934,-4.1719 -0.7307,-3.0216 1.0812,-6.0525 4.0469,-6.7695 0.3902,-0.094 0.7897,-0.1445 1.1914,-0.1504 z"
transform="translate(5.0092774e-5,-757.87625)" />
</g>
</g>
<g
style="display:inline;opacity:1;fill:#fee685;fill-opacity:1;stroke-width:0.755906;stroke-miterlimit:4;stroke-dasharray:none"
id="g10-2"
transform="matrix(-0.50509374,0.06754889,0.06754889,0.50509374,156.75523,55.243465)">
<g
style="fill:#fee685;fill-opacity:1"
id="g9-1"
transform="translate(-20.244579,-6.1209206)">
<g
style="fill:#fee685;fill-opacity:1"
id="g8-6"
transform="translate(61.54776,-5.6726683)">
<g
id="g7-8"
style="fill:#fee685;fill-opacity:1">
<g
id="g6-5"
transform="matrix(-0.514626,0.06882369,0.06882369,0.514626,1184.3644,-811.9091)"
style="opacity:1;fill:#fee685;fill-opacity:1">
<path
id="path4-7"
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#fee685;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.02362;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
d="m 2230.8945,2301.1738 c -1.9108,-0.039 -3.9117,0.162 -5.9785,0.6328 -0.1394,0.032 -0.2613,0.071 -0.3984,0.1036 -2.274,2.2481 -4.8127,4.5047 -7.5293,6.7168 0.8746,3.8597 2.1735,7.8829 4.4707,12.0019 3.9872,7.1495 7.2742,10.9657 9.2031,15.9258 1.9289,4.9601 2.2639,10.7945 0.4746,21.4238 -2.2183,13.178 -10.2404,27.1324 -22.959,37.4336 9.8717,-2.8792 18.2866,-8.1915 23.8575,-14.6269 6.0132,-6.9464 8.0191,-10.8762 8.7226,-14.836 0.7036,-3.9598 0.044,-8.2997 0.3242,-15.664 0.1805,-4.7447 1.1911,-8.8958 2.4766,-12.545 l 0.3086,-0.875 0.9219,-0.1211 c 8.2284,-1.0673 15.6654,-3.167 19.5097,-5.6484 -1.2349,-5.5522 -6.4807,-9.8603 -13.4277,-13.1348 l -0.6621,-0.3125 -0.166,-0.7129 c -2.2034,-9.4614 -9.5905,-15.5632 -19.1485,-15.7617 z m 4.7832,11.6856 a 4.8229105,4.9139092 17.729059 0 1 1.4473,0.2246 4.8229105,4.9139092 17.729059 0 1 3.0977,6.1484 4.8229105,4.9139092 17.729059 0 1 -6.0899,3.2129 4.8229105,4.9139092 17.729059 0 1 -3.0976,-6.1484 4.8229105,4.9139092 17.729059 0 1 4.6425,-3.4375 z"
transform="translate(1.2499985e-4,-757.87627)" />
</g>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,6 +1,6 @@
use bevy_ecs::prelude::*;
#[derive(Event)]
#[derive(Event, BufferedEvent)]
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
pub struct Benchmark<const SIZE: usize>(Events<BenchEvent<SIZE>>);

View File

@ -1,6 +1,6 @@
use bevy_ecs::prelude::*;
#[derive(Event)]
#[derive(Event, BufferedEvent)]
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
impl<const SIZE: usize> Default for BenchEvent<SIZE> {

View File

@ -61,14 +61,10 @@ pub fn event_propagation(criterion: &mut Criterion) {
group.finish();
}
#[derive(Clone, Component)]
#[derive(Event, EntityEvent, Clone, Component)]
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
struct TestEvent<const N: usize> {}
impl<const N: usize> Event for TestEvent<N> {
type Traversal = &'static ChildOf;
const AUTO_PROPAGATE: bool = true;
}
fn send_events<const N: usize, const N_EVENTS: usize>(world: &mut World, leaves: &[Entity]) {
let target = leaves.iter().choose(&mut rand::thread_rng()).unwrap();
@ -117,6 +113,6 @@ fn add_listeners_to_hierarchy<const DENSITY: usize, const N: usize>(
}
}
fn empty_listener<const N: usize>(trigger: Trigger<TestEvent<N>>) {
fn empty_listener<const N: usize>(trigger: On<TestEvent<N>>) {
black_box(trigger);
}

View File

@ -1,8 +1,8 @@
use core::hint::black_box;
use bevy_ecs::{
event::Event,
observer::{Trigger, TriggerTargets},
event::{EntityEvent, Event},
observer::{On, TriggerTargets},
world::World,
};
@ -13,7 +13,7 @@ fn deterministic_rand() -> ChaCha8Rng {
ChaCha8Rng::seed_from_u64(42)
}
#[derive(Clone, Event)]
#[derive(Clone, Event, EntityEvent)]
struct EventBase;
pub fn observe_simple(criterion: &mut Criterion) {
@ -46,7 +46,7 @@ pub fn observe_simple(criterion: &mut Criterion) {
group.finish();
}
fn empty_listener_base(trigger: Trigger<EventBase>) {
fn empty_listener_base(trigger: On<EventBase>) {
black_box(trigger);
}

View File

@ -26,7 +26,8 @@ use accesskit::Node;
use bevy_app::Plugin;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
prelude::{Component, Event},
component::Component,
event::{BufferedEvent, Event},
resource::Resource,
schedule::SystemSet,
};
@ -44,7 +45,7 @@ use serde::{Deserialize, Serialize};
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
#[derive(Event, Deref, DerefMut)]
#[derive(Event, BufferedEvent, Deref, DerefMut)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct ActionRequest(pub accesskit::ActionRequest);

View File

@ -32,7 +32,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-fea
# other
petgraph = { version = "0.7", features = ["serde-1"] }
ron = "0.8"
ron = "0.10"
serde = "1"
blake3 = { version = "1.0" }
downcast-rs = { version = "2", default-features = false, features = ["std"] }

View File

@ -1,10 +1,11 @@
//! The animation graph, which allows animations to be blended together.
use core::{
fmt::Write,
iter,
ops::{Index, IndexMut, Range},
};
use std::io::{self, Write};
use std::io;
use bevy_asset::{
io::Reader, Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets, Handle, LoadContext,

View File

@ -324,13 +324,13 @@ impl AnimationClip {
.push(variable_curve);
}
/// Add a untargeted [`Event`] to this [`AnimationClip`].
/// Add an [`EntityEvent`] with no [`AnimationTarget`] to this [`AnimationClip`].
///
/// The `event` will be cloned and triggered on the [`AnimationPlayer`] entity once the `time` (in seconds)
/// is reached in the animation.
///
/// See also [`add_event_to_target`](Self::add_event_to_target).
pub fn add_event(&mut self, time: f32, event: impl Event + Clone) {
pub fn add_event(&mut self, time: f32, event: impl EntityEvent + Clone) {
self.add_event_fn(
time,
move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| {
@ -339,7 +339,7 @@ impl AnimationClip {
);
}
/// Add an [`Event`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
/// Add an [`EntityEvent`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
///
/// The `event` will be cloned and triggered on the entity matching the target once the `time` (in seconds)
/// is reached in the animation.
@ -349,7 +349,7 @@ impl AnimationClip {
&mut self,
target_id: AnimationTargetId,
time: f32,
event: impl Event + Clone,
event: impl EntityEvent + Clone,
) {
self.add_event_fn_to_target(
target_id,
@ -360,19 +360,19 @@ impl AnimationClip {
);
}
/// Add a untargeted event function to this [`AnimationClip`].
/// Add an event function with no [`AnimationTarget`] to this [`AnimationClip`].
///
/// The `func` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds)
/// is reached in the animation.
///
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event`].
/// For a simpler [`EntityEvent`]-based alternative, see [`AnimationClip::add_event`].
/// See also [`add_event_to_target`](Self::add_event_to_target).
///
/// ```
/// # use bevy_animation::AnimationClip;
/// # let mut clip = AnimationClip::default();
/// clip.add_event_fn(1.0, |commands, entity, time, weight| {
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
/// println!("Animation event triggered {entity:#?} at time {time} with weight {weight}");
/// })
/// ```
pub fn add_event_fn(
@ -388,14 +388,14 @@ impl AnimationClip {
/// The `func` will trigger on the entity matching the target once the `time` (in seconds)
/// is reached in the animation.
///
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event_to_target`].
/// For a simpler [`EntityEvent`]-based alternative, see [`AnimationClip::add_event_to_target`].
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
///
/// ```
/// # use bevy_animation::{AnimationClip, AnimationTargetId};
/// # let mut clip = AnimationClip::default();
/// clip.add_event_fn_to_target(AnimationTargetId::from_iter(["Arm", "Hand"]), 1.0, |commands, entity, time, weight| {
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
/// println!("Animation event triggered {entity:#?} at time {time} with weight {weight}");
/// })
/// ```
pub fn add_event_fn_to_target(
@ -1534,7 +1534,7 @@ mod tests {
use super::*;
#[derive(Event, Reflect, Clone)]
#[derive(Event, EntityEvent, Reflect, Clone)]
struct A;
#[track_caller]

View File

@ -847,7 +847,7 @@ impl ViewNode for SmaaNode {
view_smaa_uniform_offset,
smaa_textures,
view_smaa_bind_groups,
): QueryItem<'w, Self::ViewQuery>,
): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let pipeline_cache = world.resource::<PipelineCache>();

View File

@ -106,6 +106,8 @@ impl Default for App {
#[cfg(feature = "bevy_reflect")]
{
use bevy_ecs::observer::ObservedBy;
#[cfg(not(feature = "reflect_auto_register"))]
app.init_resource::<AppTypeRegistry>();
@ -115,6 +117,7 @@ impl Default for App {
app.register_type::<Name>();
app.register_type::<ChildOf>();
app.register_type::<Children>();
app.register_type::<ObservedBy>();
}
#[cfg(feature = "reflect_functions")]
@ -346,7 +349,7 @@ impl App {
self
}
/// Initializes `T` event handling by inserting an event queue resource ([`Events::<T>`])
/// Initializes [`BufferedEvent`] handling for `T` by inserting an event queue resource ([`Events::<T>`])
/// and scheduling an [`event_update_system`] in [`First`].
///
/// See [`Events`] for information on how to define events.
@ -357,7 +360,7 @@ impl App {
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # #[derive(Event, BufferedEvent)]
/// # struct MyEvent;
/// # let mut app = App::new();
/// #
@ -365,7 +368,7 @@ impl App {
/// ```
pub fn add_event<T>(&mut self) -> &mut Self
where
T: Event,
T: BufferedEvent,
{
self.main_mut().add_event::<T>();
self
@ -1311,7 +1314,7 @@ impl App {
/// Spawns an [`Observer`] entity, which will watch for and respond to the given event.
///
/// `observer` can be any system whose first parameter is a [`Trigger`].
/// `observer` can be any system whose first parameter is [`On`].
///
/// # Examples
///
@ -1327,14 +1330,14 @@ impl App {
/// # friends_allowed: bool,
/// # };
/// #
/// # #[derive(Event)]
/// # #[derive(Event, EntityEvent)]
/// # struct Invite;
/// #
/// # #[derive(Component)]
/// # struct Friend;
/// #
///
/// app.add_observer(|trigger: Trigger<Party>, friends: Query<Entity, With<Friend>>, mut commands: Commands| {
/// app.add_observer(|trigger: On<Party>, friends: Query<Entity, With<Friend>>, mut commands: Commands| {
/// if trigger.event().friends_allowed {
/// for friend in friends.iter() {
/// commands.trigger_targets(Invite, friend);
@ -1409,7 +1412,7 @@ fn run_once(mut app: App) -> AppExit {
app.should_exit().unwrap_or(AppExit::Success)
}
/// An event that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
/// A [`BufferedEvent`] that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
/// the [runner](App::set_runner) will end and ([maybe](App::run)) return control to the caller.
///
/// This event can be used to detect when an exit is requested. Make sure that systems listening
@ -1419,7 +1422,7 @@ fn run_once(mut app: App) -> AppExit {
/// This type is roughly meant to map to a standard definition of a process exit code (0 means success, not 0 means error). Due to portability concerns
/// (see [`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) and [`process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html#))
/// we only allow error codes between 1 and [255](u8::MAX).
#[derive(Event, Debug, Clone, Default, PartialEq, Eq)]
#[derive(Event, BufferedEvent, Debug, Clone, Default, PartialEq, Eq)]
pub enum AppExit {
/// [`App`] exited without any problems.
#[default]
@ -1487,9 +1490,9 @@ mod tests {
change_detection::{DetectChanges, ResMut},
component::Component,
entity::Entity,
event::{Event, EventWriter, Events},
event::{BufferedEvent, Event, EventWriter, Events},
lifecycle::RemovedComponents,
query::With,
removal_detection::RemovedComponents,
resource::Resource,
schedule::{IntoScheduleConfigs, ScheduleLabel},
system::{Commands, Query},
@ -1853,7 +1856,7 @@ mod tests {
}
#[test]
fn events_should_be_updated_once_per_update() {
#[derive(Event, Clone)]
#[derive(Event, BufferedEvent, Clone)]
struct TestEvent;
let mut app = App::new();

View File

@ -6,9 +6,9 @@ use bevy_ecs::{
component::Component,
entity::Entity,
hierarchy::ChildOf,
lifecycle::RemovedComponents,
query::{Changed, Or, QueryFilter, With, Without},
relationship::{Relationship, RelationshipTarget},
removal_detection::RemovedComponents,
schedule::{IntoScheduleConfigs, SystemSet},
system::{Commands, Local, Query},
};

View File

@ -338,7 +338,7 @@ impl SubApp {
/// See [`App::add_event`].
pub fn add_event<T>(&mut self) -> &mut Self
where
T: Event,
T: BufferedEvent,
{
if !self.world.contains_resource::<Events<T>>() {
EventRegistry::register_event::<T>(self.world_mut());

View File

@ -54,7 +54,7 @@ parking_lot = { version = "0.12", default-features = false, features = [
"arc_lock",
"send_guard",
] }
ron = { version = "0.8", default-features = false }
ron = { version = "0.10", default-features = false }
serde = { version = "1", default-features = false, features = ["derive"] }
thiserror = { version = "2", default-features = false }
derive_more = { version = "1", default-features = false, features = ["from"] }

View File

@ -1,6 +1,7 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
//! Macros for deriving asset traits.
use bevy_macro_utils::BevyManifest;
use proc_macro::{Span, TokenStream};
use quote::{format_ident, quote};
@ -12,6 +13,7 @@ pub(crate) fn bevy_asset_path() -> Path {
const DEPENDENCY_ATTRIBUTE: &str = "dependency";
/// Implement the `Asset` trait.
#[proc_macro_derive(Asset, attributes(dependency))]
pub fn derive_asset(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
@ -30,6 +32,7 @@ pub fn derive_asset(input: TokenStream) -> TokenStream {
})
}
/// Implement the `VisitAssetDependencies` trait.
#[proc_macro_derive(VisitAssetDependencies, attributes(dependency))]
pub fn derive_asset_dependency_visitor(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);

View File

@ -158,9 +158,9 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
fetch
}
unsafe fn init_fetch<'w>(
unsafe fn init_fetch<'w, 's>(
world: UnsafeWorldCell<'w>,
state: &Self::State,
state: &'s Self::State,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
@ -201,9 +201,9 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
const IS_DENSE: bool = <&A>::IS_DENSE;
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
state: &'s Self::State,
archetype: &'w Archetype,
table: &'w Table,
) {
@ -215,7 +215,11 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
}
}
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
unsafe fn set_table<'w, 's>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
table: &'w Table,
) {
if let Some(inner) = &mut fetch.inner {
// SAFETY: We delegate to the inner `set_table` for `A`
unsafe {
@ -265,6 +269,7 @@ unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
#[inline]
unsafe fn filter_fetch(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
@ -272,7 +277,7 @@ unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
fetch.inner.as_mut().is_some_and(|inner| {
// SAFETY: We delegate to the inner `fetch` for `A`
unsafe {
let handle = <&A>::fetch(inner, entity, table_row);
let handle = <&A>::fetch(&state.asset_id, inner, entity, table_row);
fetch.check.has_changed(handle)
}
})

View File

@ -1,12 +1,12 @@
use crate::{Asset, AssetId, AssetLoadError, AssetPath, UntypedAssetId};
use bevy_ecs::event::Event;
use bevy_ecs::event::{BufferedEvent, Event};
use bevy_reflect::Reflect;
use core::fmt::Debug;
/// An event emitted when a specific [`Asset`] fails to load.
/// A [`BufferedEvent`] emitted when a specific [`Asset`] fails to load.
///
/// For an untyped equivalent, see [`UntypedAssetLoadFailedEvent`].
#[derive(Event, Clone, Debug)]
#[derive(Event, BufferedEvent, Clone, Debug)]
pub struct AssetLoadFailedEvent<A: Asset> {
/// The stable identifier of the asset that failed to load.
pub id: AssetId<A>,
@ -24,7 +24,7 @@ impl<A: Asset> AssetLoadFailedEvent<A> {
}
/// An untyped version of [`AssetLoadFailedEvent`].
#[derive(Event, Clone, Debug)]
#[derive(Event, BufferedEvent, Clone, Debug)]
pub struct UntypedAssetLoadFailedEvent {
/// The stable identifier of the asset that failed to load.
pub id: UntypedAssetId,
@ -44,9 +44,9 @@ impl<A: Asset> From<&AssetLoadFailedEvent<A>> for UntypedAssetLoadFailedEvent {
}
}
/// Events that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
/// [`BufferedEvent`]s that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
#[expect(missing_docs, reason = "Documenting the id fields is unhelpful.")]
#[derive(Event, Reflect)]
#[derive(Event, BufferedEvent, Reflect)]
pub enum AssetEvent<A: Asset> {
/// Emitted whenever an [`Asset`] is added.
Added { id: AssetId<A> },

View File

@ -344,7 +344,7 @@ impl<'a> LoadContext<'a> {
/// Begins a new labeled asset load. Use the returned [`LoadContext`] to load
/// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load.
/// When finished, make sure you call [`LoadContext::add_labeled_asset`] to add the results back to the parent
/// When finished, make sure you call [`LoadContext::add_loaded_labeled_asset`] to add the results back to the parent
/// context.
/// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add
/// the labeled [`LoadContext`] back to the parent context.
@ -360,7 +360,7 @@ impl<'a> LoadContext<'a> {
/// # let load_context: LoadContext = panic!();
/// let mut handles = Vec::new();
/// for i in 0..2 {
/// let mut labeled = load_context.begin_labeled_asset();
/// let labeled = load_context.begin_labeled_asset();
/// handles.push(std::thread::spawn(move || {
/// (i.to_string(), labeled.finish(Image::default()))
/// }));
@ -385,7 +385,7 @@ impl<'a> LoadContext<'a> {
/// [`LoadedAsset`], which is registered under the `label` label.
///
/// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the
/// result with [`LoadContext::add_labeled_asset`].
/// result with [`LoadContext::add_loaded_labeled_asset`].
///
/// See [`AssetPath`] for more on labeled assets.
pub fn labeled_asset_scope<A: Asset, E>(

View File

@ -18,16 +18,16 @@ pub struct ReflectAsset {
handle_type_id: TypeId,
assets_resource_type_id: TypeId,
get: fn(&World, UntypedHandle) -> Option<&dyn Reflect>,
get: fn(&World, UntypedAssetId) -> Option<&dyn Reflect>,
// SAFETY:
// - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets<T>` resource mutably
// - may only be used to access **at most one** access at once
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedHandle) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedAssetId) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
insert: fn(&mut World, UntypedHandle, &dyn PartialReflect),
insert: fn(&mut World, UntypedAssetId, &dyn PartialReflect),
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
remove: fn(&mut World, UntypedHandle) -> Option<Box<dyn Reflect>>,
remove: fn(&mut World, UntypedAssetId) -> Option<Box<dyn Reflect>>,
}
impl ReflectAsset {
@ -42,15 +42,19 @@ impl ReflectAsset {
}
/// Equivalent of [`Assets::get`]
pub fn get<'w>(&self, world: &'w World, handle: UntypedHandle) -> Option<&'w dyn Reflect> {
(self.get)(world, handle)
pub fn get<'w>(
&self,
world: &'w World,
asset_id: impl Into<UntypedAssetId>,
) -> Option<&'w dyn Reflect> {
(self.get)(world, asset_id.into())
}
/// Equivalent of [`Assets::get_mut`]
pub fn get_mut<'w>(
&self,
world: &'w mut World,
handle: UntypedHandle,
asset_id: impl Into<UntypedAssetId>,
) -> Option<&'w mut dyn Reflect> {
// SAFETY: unique world access
#[expect(
@ -58,7 +62,7 @@ impl ReflectAsset {
reason = "Use of unsafe `Self::get_unchecked_mut()` function."
)]
unsafe {
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), handle)
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), asset_id.into())
}
}
@ -76,8 +80,8 @@ impl ReflectAsset {
/// # let handle_1: UntypedHandle = unimplemented!();
/// # let handle_2: UntypedHandle = unimplemented!();
/// let unsafe_world_cell = world.as_unsafe_world_cell();
/// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_1).unwrap() };
/// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_2).unwrap() };
/// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_1).unwrap() };
/// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_2).unwrap() };
/// // ^ not allowed, two mutable references through the same asset resource, even though the
/// // handles are distinct
///
@ -96,10 +100,10 @@ impl ReflectAsset {
pub unsafe fn get_unchecked_mut<'w>(
&self,
world: UnsafeWorldCell<'w>,
handle: UntypedHandle,
asset_id: impl Into<UntypedAssetId>,
) -> Option<&'w mut dyn Reflect> {
// SAFETY: requirements are deferred to the caller
unsafe { (self.get_unchecked_mut)(world, handle) }
unsafe { (self.get_unchecked_mut)(world, asset_id.into()) }
}
/// Equivalent of [`Assets::add`]
@ -107,13 +111,22 @@ impl ReflectAsset {
(self.add)(world, value)
}
/// Equivalent of [`Assets::insert`]
pub fn insert(&self, world: &mut World, handle: UntypedHandle, value: &dyn PartialReflect) {
(self.insert)(world, handle, value);
pub fn insert(
&self,
world: &mut World,
asset_id: impl Into<UntypedAssetId>,
value: &dyn PartialReflect,
) {
(self.insert)(world, asset_id.into(), value);
}
/// Equivalent of [`Assets::remove`]
pub fn remove(&self, world: &mut World, handle: UntypedHandle) -> Option<Box<dyn Reflect>> {
(self.remove)(world, handle)
pub fn remove(
&self,
world: &mut World,
asset_id: impl Into<UntypedAssetId>,
) -> Option<Box<dyn Reflect>> {
(self.remove)(world, asset_id.into())
}
/// Equivalent of [`Assets::len`]
@ -137,17 +150,17 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
ReflectAsset {
handle_type_id: TypeId::of::<Handle<A>>(),
assets_resource_type_id: TypeId::of::<Assets<A>>(),
get: |world, handle| {
get: |world, asset_id| {
let assets = world.resource::<Assets<A>>();
let asset = assets.get(&handle.typed_debug_checked());
let asset = assets.get(asset_id.typed_debug_checked());
asset.map(|asset| asset as &dyn Reflect)
},
get_unchecked_mut: |world, handle| {
get_unchecked_mut: |world, asset_id| {
// SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
// and must ensure to only have at most one reference to it live at all times.
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
let asset = assets.get_mut(&handle.typed_debug_checked());
let asset = assets.get_mut(asset_id.typed_debug_checked());
asset.map(|asset| asset as &mut dyn Reflect)
},
add: |world, value| {
@ -156,11 +169,11 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
assets.add(value).untyped()
},
insert: |world, handle, value| {
insert: |world, asset_id, value| {
let mut assets = world.resource_mut::<Assets<A>>();
let value: A = FromReflect::from_reflect(value)
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
assets.insert(&handle.typed_debug_checked(), value);
assets.insert(asset_id.typed_debug_checked(), value);
},
len: |world| {
let assets = world.resource::<Assets<A>>();
@ -170,9 +183,9 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
let assets = world.resource::<Assets<A>>();
Box::new(assets.ids().map(AssetId::untyped))
},
remove: |world, handle| {
remove: |world, asset_id| {
let mut assets = world.resource_mut::<Assets<A>>();
let value = assets.remove(&handle.typed_debug_checked());
let value = assets.remove(asset_id.typed_debug_checked());
value.map(|value| Box::new(value) as Box<dyn Reflect>)
},
}
@ -200,7 +213,7 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
/// let reflect_asset = type_registry.get_type_data::<ReflectAsset>(reflect_handle.asset_type_id()).unwrap();
///
/// let handle = reflect_handle.downcast_handle_untyped(handle.as_any()).unwrap();
/// let value = reflect_asset.get(world, handle).unwrap();
/// let value = reflect_asset.get(world, &handle).unwrap();
/// println!("{value:?}");
/// }
/// ```
@ -247,7 +260,7 @@ mod tests {
use alloc::{string::String, vec::Vec};
use core::any::TypeId;
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset, UntypedHandle};
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset};
use bevy_app::App;
use bevy_ecs::reflect::AppTypeRegistry;
use bevy_reflect::Reflect;
@ -281,7 +294,7 @@ mod tests {
let handle = reflect_asset.add(app.world_mut(), &value);
// struct is a reserved keyword, so we can't use it here
let strukt = reflect_asset
.get_mut(app.world_mut(), handle)
.get_mut(app.world_mut(), &handle)
.unwrap()
.reflect_mut()
.as_struct()
@ -294,16 +307,12 @@ mod tests {
assert_eq!(reflect_asset.len(app.world()), 1);
let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
assert_eq!(ids.len(), 1);
let id = ids[0];
let fetched_handle = UntypedHandle::Weak(ids[0]);
let asset = reflect_asset
.get(app.world(), fetched_handle.clone_weak())
.unwrap();
let asset = reflect_asset.get(app.world(), id).unwrap();
assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
reflect_asset
.remove(app.world_mut(), fetched_handle)
.unwrap();
reflect_asset.remove(app.world_mut(), id).unwrap();
assert_eq!(reflect_asset.len(app.world()), 0);
}
}

View File

@ -1953,6 +1953,14 @@ impl AssetLoaderError {
pub fn path(&self) -> &AssetPath<'static> {
&self.path
}
/// The error the loader reported when attempting to load the asset.
///
/// If you know the type of the error the asset loader returned, you can use
/// [`BevyError::downcast_ref()`] to get it.
pub fn error(&self) -> &BevyError {
&self.error
}
}
/// An error that occurs while resolving an asset added by `add_async`.

View File

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

View File

@ -92,7 +92,7 @@ impl Hsla {
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Hsla::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
pub const fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;

View File

@ -96,7 +96,7 @@ impl Lcha {
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Lcha::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
pub const fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;

View File

@ -92,7 +92,7 @@ impl Oklcha {
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Oklcha::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
pub const fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;

View File

@ -121,7 +121,7 @@ impl ViewNode for BloomNode {
bloom_settings,
upsampling_pipeline_ids,
downsampling_pipeline_ids,
): QueryItem<'w, Self::ViewQuery>,
): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
if bloom_settings.intensity == 0.0 {

View File

@ -227,7 +227,7 @@ impl ExtractComponent for Bloom {
type QueryFilter = With<Hdr>;
type Out = (Self, BloomUniforms);
fn extract_component((bloom, camera): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
fn extract_component((bloom, camera): QueryItem<'_, '_, Self::QueryData>) -> Option<Self::Out> {
match (
camera.physical_viewport_rect(),
camera.physical_viewport_size(),

View File

@ -31,7 +31,7 @@ impl ViewNode for MainOpaquePass2dNode {
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(camera, view, target, depth): QueryItem<'w, Self::ViewQuery>,
(camera, view, target, depth): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let (Some(opaque_phases), Some(alpha_mask_phases)) = (

View File

@ -28,7 +28,7 @@ impl ViewNode for MainTransparentPass2dNode {
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(camera, view, target, depth): bevy_ecs::query::QueryItem<'w, Self::ViewQuery>,
(camera, view, target, depth): bevy_ecs::query::QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let Some(transparent_phases) =

View File

@ -45,7 +45,7 @@ impl ViewNode for MainOpaquePass3dNode {
skybox_pipeline,
skybox_bind_group,
view_uniform_offset,
): QueryItem<'w, Self::ViewQuery>,
): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let (Some(opaque_phases), Some(alpha_mask_phases)) = (

View File

@ -36,7 +36,7 @@ impl ViewNode for EarlyDeferredGBufferPrepassNode {
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
view_query: QueryItem<'w, Self::ViewQuery>,
view_query: QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
run_deferred_prepass(
@ -74,7 +74,7 @@ impl ViewNode for LateDeferredGBufferPrepassNode {
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
view_query: QueryItem<'w, Self::ViewQuery>,
view_query: QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let (_, _, _, _, occlusion_culling, no_indirect_drawing) = view_query;
@ -107,6 +107,7 @@ fn run_deferred_prepass<'w>(
render_context: &mut RenderContext<'w>,
(camera, extracted_view, view_depth_texture, view_prepass_textures, _, _): QueryItem<
'w,
'_,
<LateDeferredGBufferPrepassNode as ViewNode>::ViewQuery,
>,
is_late: bool,

View File

@ -352,7 +352,7 @@ impl ViewNode for DepthOfFieldNode {
view_bind_group_layouts,
depth_of_field_uniform_index,
auxiliary_dof_texture,
): QueryItem<'w, Self::ViewQuery>,
): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let pipeline_cache = world.resource::<PipelineCache>();

View File

@ -61,7 +61,7 @@ impl ViewNode for MsaaWritebackNode {
&self,
_graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(target, blit_pipeline_id, msaa): QueryItem<'w, Self::ViewQuery>,
(target, blit_pipeline_id, msaa): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
if *msaa == Msaa::Off {

View File

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

View File

@ -352,7 +352,7 @@ impl ViewNode for PostProcessingNode {
&self,
_: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
(view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, Self::ViewQuery>,
(view_target, pipeline_id, chromatic_aberration, post_processing_uniform_buffer_offsets): QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
let pipeline_cache = world.resource::<PipelineCache>();
@ -485,7 +485,7 @@ impl ExtractComponent for ChromaticAberration {
type Out = ChromaticAberration;
fn extract_component(
chromatic_aberration: QueryItem<'_, Self::QueryData>,
chromatic_aberration: QueryItem<'_, '_, Self::QueryData>,
) -> Option<Self::Out> {
// Skip the postprocessing phase entirely if the intensity is zero.
if chromatic_aberration.intensity > 0.0 {

View File

@ -74,11 +74,16 @@ pub struct MotionVectorPrepass;
#[reflect(Component, Default)]
pub struct DeferredPrepass;
/// View matrices from the previous frame.
///
/// Useful for temporal rendering techniques that need access to last frame's camera data.
#[derive(Component, ShaderType, Clone)]
pub struct PreviousViewData {
pub view_from_world: Mat4,
pub clip_from_world: Mat4,
pub clip_from_view: Mat4,
pub world_from_clip: Mat4,
pub view_from_clip: Mat4,
}
#[derive(Resource, Default)]

View File

@ -36,7 +36,7 @@ impl ViewNode for EarlyPrepassNode {
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
view_query: QueryItem<'w, Self::ViewQuery>,
view_query: QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
run_prepass(graph, render_context, view_query, world, "early prepass")
@ -73,7 +73,7 @@ impl ViewNode for LatePrepassNode {
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext<'w>,
query: QueryItem<'w, Self::ViewQuery>,
query: QueryItem<'w, '_, Self::ViewQuery>,
world: &'w World,
) -> Result<(), NodeRunError> {
// We only need a late prepass if we have occlusion culling and indirect
@ -112,7 +112,7 @@ fn run_prepass<'w>(
_,
_,
has_deferred,
): QueryItem<'w, <LatePrepassNode as ViewNode>::ViewQuery>,
): QueryItem<'w, '_, <LatePrepassNode as ViewNode>::ViewQuery>,
world: &'w World,
label: &'static str,
) -> Result<(), NodeRunError> {

View File

@ -113,7 +113,9 @@ impl ExtractComponent for Skybox {
type QueryFilter = ();
type Out = (Self, SkyboxUniforms);
fn extract_component((skybox, exposure): QueryItem<'_, Self::QueryData>) -> Option<Self::Out> {
fn extract_component(
(skybox, exposure): QueryItem<'_, '_, Self::QueryData>,
) -> Option<Self::Out> {
let exposure = exposure
.map(Exposure::exposure)
.unwrap_or_else(|| Exposure::default().exposure());
@ -123,7 +125,7 @@ impl ExtractComponent for Skybox {
SkyboxUniforms {
brightness: skybox.brightness * exposure,
transform: Transform::from_rotation(skybox.rotation)
.compute_matrix()
.to_matrix()
.inverse(),
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
_wasm_padding_8b: 0,

View File

@ -0,0 +1,38 @@
[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_log = { path = "../bevy_log", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev", features = [
"bevy_ui_picking_backend",
] }
# other
accesskit = "0.19"
[features]
default = []
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
all-features = true

View File

@ -0,0 +1,141 @@
use accesskit::Role;
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin};
use bevy_ecs::query::Has;
use bevy_ecs::system::ResMut;
use bevy_ecs::{
component::Component,
entity::Entity,
observer::On,
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: On<FocusedInput<KeyboardInput>>,
q_state: Query<(&CoreButton, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((bstate, disabled)) = q_state.get(trigger.target()) {
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: On<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()) {
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: On<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()) {
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() != Entity::PLACEHOLDER).then_some(trigger.target());
}
if let Some(mut focus_visible) = focus_visible {
focus_visible.0 = false;
}
}
}
}
fn button_on_pointer_up(
mut trigger: On<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()) {
trigger.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
fn button_on_pointer_drag_end(
mut trigger: On<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()) {
trigger.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
fn button_on_pointer_cancel(
mut trigger: On<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()) {
trigger.propagate(false);
if !disabled && pressed {
commands.entity(button).remove::<Pressed>();
}
}
}
/// Plugin that adds the observers for the [`CoreButton`] widget.
pub struct CoreButtonPlugin;
impl Plugin for CoreButtonPlugin {
fn build(&self, app: &mut App) {
app.add_observer(button_on_key_event)
.add_observer(button_on_pointer_down)
.add_observer(button_on_pointer_up)
.add_observer(button_on_pointer_click)
.add_observer(button_on_pointer_drag_end)
.add_observer(button_on_pointer_cancel);
}
}

View File

@ -0,0 +1,179 @@
use accesskit::Role;
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin};
use bevy_ecs::event::{EntityEvent, Event};
use bevy_ecs::query::{Has, Without};
use bevy_ecs::system::{In, ResMut};
use bevy_ecs::{
component::Component,
observer::On,
system::{Commands, Query, SystemId},
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input::ButtonState;
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_picking::events::{Click, Pointer};
use bevy_ui::{Checkable, Checked, InteractionDisabled};
/// Headless widget implementation for checkboxes. The [`Checked`] component represents the current
/// state of the checkbox. The `on_change` field is an optional system id that will be run when the
/// checkbox is clicked, or when the `Enter` or `Space` key is pressed while the checkbox is
/// focused. If the `on_change` field is `None`, then instead of calling a callback, the checkbox
/// will update its own [`Checked`] state directly.
///
/// # Toggle switches
///
/// The [`CoreCheckbox`] component can be used to implement other kinds of toggle widgets. If you
/// are going to do a toggle switch, you should override the [`AccessibilityNode`] component with
/// the `Switch` role instead of the `Checkbox` role.
#[derive(Component, Debug, Default)]
#[require(AccessibilityNode(accesskit::Node::new(Role::CheckBox)), Checkable)]
pub struct CoreCheckbox {
/// One-shot system that is run when the checkbox state needs to be changed.
pub on_change: Option<SystemId<In<bool>>>,
}
fn checkbox_on_key_input(
mut ev: On<FocusedInput<KeyboardInput>>,
q_checkbox: Query<(&CoreCheckbox, Has<Checked>), Without<InteractionDisabled>>,
mut commands: Commands,
) {
if let Ok((checkbox, is_checked)) = q_checkbox.get(ev.target()) {
let event = &ev.event().input;
if event.state == ButtonState::Pressed
&& !event.repeat
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
{
ev.propagate(false);
set_checkbox_state(&mut commands, ev.target(), checkbox, !is_checked);
}
}
}
fn checkbox_on_pointer_click(
mut ev: On<Pointer<Click>>,
q_checkbox: Query<(&CoreCheckbox, Has<Checked>, Has<InteractionDisabled>)>,
focus: Option<ResMut<InputFocus>>,
focus_visible: Option<ResMut<InputFocusVisible>>,
mut commands: Commands,
) {
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(ev.target()) {
// 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 = Some(ev.target());
}
if let Some(mut focus_visible) = focus_visible {
focus_visible.0 = false;
}
ev.propagate(false);
if !disabled {
set_checkbox_state(&mut commands, ev.target(), checkbox, !is_checked);
}
}
}
/// Event which can be triggered on a checkbox to set the checked state. This can be used to control
/// the checkbox via gamepad buttons or other inputs.
///
/// # Example:
///
/// ```
/// use bevy_ecs::system::Commands;
/// use bevy_core_widgets::{CoreCheckbox, SetChecked};
///
/// fn setup(mut commands: Commands) {
/// // Create a checkbox
/// let checkbox = commands.spawn((
/// CoreCheckbox::default(),
/// )).id();
///
/// // Set to checked
/// commands.trigger_targets(SetChecked(true), checkbox);
/// }
/// ```
#[derive(Event, EntityEvent)]
pub struct SetChecked(pub bool);
/// Event which can be triggered on a checkbox to toggle the checked state. This can be used to
/// control the checkbox via gamepad buttons or other inputs.
///
/// # Example:
///
/// ```
/// use bevy_ecs::system::Commands;
/// use bevy_core_widgets::{CoreCheckbox, ToggleChecked};
///
/// fn setup(mut commands: Commands) {
/// // Create a checkbox
/// let checkbox = commands.spawn((
/// CoreCheckbox::default(),
/// )).id();
///
/// // Set to checked
/// commands.trigger_targets(ToggleChecked, checkbox);
/// }
/// ```
#[derive(Event, EntityEvent)]
pub struct ToggleChecked;
fn checkbox_on_set_checked(
mut ev: On<SetChecked>,
q_checkbox: Query<(&CoreCheckbox, Has<Checked>, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(ev.target()) {
ev.propagate(false);
if disabled {
return;
}
let will_be_checked = ev.event().0;
if will_be_checked != is_checked {
set_checkbox_state(&mut commands, ev.target(), checkbox, will_be_checked);
}
}
}
fn checkbox_on_toggle_checked(
mut ev: On<ToggleChecked>,
q_checkbox: Query<(&CoreCheckbox, Has<Checked>, Has<InteractionDisabled>)>,
mut commands: Commands,
) {
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(ev.target()) {
ev.propagate(false);
if disabled {
return;
}
set_checkbox_state(&mut commands, ev.target(), checkbox, !is_checked);
}
}
fn set_checkbox_state(
commands: &mut Commands,
entity: impl Into<bevy_ecs::entity::Entity>,
checkbox: &CoreCheckbox,
new_state: bool,
) {
if let Some(on_change) = checkbox.on_change {
commands.run_system_with(on_change, new_state);
} else if new_state {
commands.entity(entity.into()).insert(Checked);
} else {
commands.entity(entity.into()).remove::<Checked>();
}
}
/// Plugin that adds the observers for the [`CoreCheckbox`] widget.
pub struct CoreCheckboxPlugin;
impl Plugin for CoreCheckboxPlugin {
fn build(&self, app: &mut App) {
app.add_observer(checkbox_on_key_input)
.add_observer(checkbox_on_pointer_click)
.add_observer(checkbox_on_set_checked)
.add_observer(checkbox_on_toggle_checked);
}
}

View File

@ -0,0 +1,514 @@
use core::ops::RangeInclusive;
use accesskit::{Orientation, Role};
use bevy_a11y::AccessibilityNode;
use bevy_app::{App, Plugin};
use bevy_ecs::entity::Entity;
use bevy_ecs::event::{EntityEvent, Event};
use bevy_ecs::hierarchy::{ChildOf, Children};
use bevy_ecs::lifecycle::Insert;
use bevy_ecs::query::Has;
use bevy_ecs::system::{In, Res, ResMut};
use bevy_ecs::world::DeferredWorld;
use bevy_ecs::{
component::Component,
observer::On,
query::With,
system::{Commands, Query, SystemId},
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input::ButtonState;
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_log::warn_once;
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};
/// Defines how the slider should behave when you click on the track (not the thumb).
#[derive(Debug, Default)]
pub enum TrackClick {
/// Clicking on the track lets you drag to edit the value, just like clicking on the thumb.
#[default]
Drag,
/// Clicking on the track increments or decrements the slider by [`SliderStep`].
Step,
/// Clicking on the track snaps the value to the clicked position.
Snap,
}
/// A headless slider widget, which can be used to build custom sliders. Sliders have a value
/// (represented by the [`SliderValue`] component) and a range (represented by [`SliderRange`]). An
/// optional step size can be specified via [`SliderStep`].
///
/// You can also control the slider remotely by triggering a [`SetSliderValue`] event on it. This
/// can be useful in a console environment for controlling the value gamepad inputs.
///
/// The presence of the `on_change` property controls whether the slider uses internal or external
/// state management. If the `on_change` property is `None`, then the slider updates its own state
/// automatically. Otherwise, the `on_change` property contains the id of a one-shot system which is
/// passed the new slider value. In this case, the slider value is not modified, it is the
/// responsibility of the callback to trigger whatever data-binding mechanism is used to update the
/// slider's value.
///
/// Typically a slider will contain entities representing the "track" and "thumb" elements. The core
/// slider makes no assumptions about the hierarchical structure of these elements, but expects that
/// the thumb will be marked with a [`CoreSliderThumb`] component.
///
/// The core slider does not modify the visible position of the thumb: that is the responsibility of
/// the stylist. This can be done either in percent or pixel units as desired. To prevent overhang
/// at the ends of the slider, the positioning should take into account the thumb width, by reducing
/// the amount of travel. So for example, in a slider 100px wide, with a thumb that is 10px, the
/// amount of travel is 90px. The core slider's calculations for clicking and dragging assume this
/// is the case, and will reduce the travel by the measured size of the thumb entity, which allows
/// the movement of the thumb to be perfectly synchronized with the movement of the mouse.
///
/// In cases where overhang is desired for artistic reasons, the thumb may have additional
/// decorative child elements, absolutely positioned, which don't affect the size measurement.
#[derive(Component, Debug, Default)]
#[require(
AccessibilityNode(accesskit::Node::new(Role::Slider)),
CoreSliderDragState,
SliderValue,
SliderRange,
SliderStep
)]
pub struct CoreSlider {
/// Callback which is called when the slider is dragged or the value is changed via other user
/// interaction. If this value is `None`, then the slider will self-update.
pub on_change: Option<SystemId<In<f32>>>,
/// Set the track-clicking behavior for this slider.
pub track_click: TrackClick,
// TODO: Think about whether we want a "vertical" option.
}
/// Marker component that identifies which descendant element is the slider thumb.
#[derive(Component, Debug, Default)]
pub struct CoreSliderThumb;
/// A component which stores the current value of the slider.
#[derive(Component, Debug, Default, PartialEq, Clone, Copy)]
#[component(immutable)]
pub struct SliderValue(pub f32);
/// A component which represents the allowed range of the slider value. Defaults to 0.0..=1.0.
#[derive(Component, Debug, PartialEq, Clone, Copy)]
#[component(immutable)]
pub struct SliderRange {
start: f32,
end: f32,
}
impl SliderRange {
/// Creates a new slider range with the given start and end values.
pub fn new(start: f32, end: f32) -> Self {
if end < start {
warn_once!(
"Expected SliderRange::start ({}) <= SliderRange::end ({})",
start,
end
);
}
Self { start, end }
}
/// Creates a new slider range from a Rust range.
pub fn from_range(range: RangeInclusive<f32>) -> Self {
let (start, end) = range.into_inner();
Self { start, end }
}
/// Returns the minimum allowed value for this slider.
pub fn start(&self) -> f32 {
self.start
}
/// Return a new instance of a `SliderRange` with a new start position.
pub fn with_start(&self, start: f32) -> Self {
Self::new(start, self.end)
}
/// Returns the maximum allowed value for this slider.
pub fn end(&self) -> f32 {
self.end
}
/// Return a new instance of a `SliderRange` with a new end position.
pub fn with_end(&self, end: f32) -> Self {
Self::new(self.start, end)
}
/// Returns the full span of the range (max - min).
pub fn span(&self) -> f32 {
self.end - self.start
}
/// Returns the center value of the range.
pub fn center(&self) -> f32 {
(self.start + self.end) / 2.0
}
/// Constrain a value between the minimum and maximum allowed values for this slider.
pub fn clamp(&self, value: f32) -> f32 {
value.clamp(self.start, self.end)
}
/// Compute the position of the thumb on the slider, as a value between 0 and 1, taking
/// into account the proportion of the value between the minimum and maximum limits.
pub fn thumb_position(&self, value: f32) -> f32 {
if self.end > self.start {
(value - self.start) / (self.end - self.start)
} else {
0.5
}
}
}
impl Default for SliderRange {
fn default() -> Self {
Self {
start: 0.0,
end: 1.0,
}
}
}
/// Defines the amount by which to increment or decrement the slider value when using keyboard
/// shorctuts. Defaults to 1.0.
#[derive(Component, Debug, PartialEq, Clone)]
#[component(immutable)]
pub struct SliderStep(pub f32);
impl Default for SliderStep {
fn default() -> Self {
Self(1.0)
}
}
/// Component used to manage the state of a slider during dragging.
#[derive(Component, Default)]
pub struct CoreSliderDragState {
/// Whether the slider is currently being dragged.
pub dragging: bool,
/// The value of the slider when dragging started.
offset: f32,
}
pub(crate) fn slider_on_pointer_down(
mut trigger: On<Pointer<Press>>,
q_slider: Query<(
&CoreSlider,
&SliderValue,
&SliderRange,
&SliderStep,
&ComputedNode,
&ComputedNodeTarget,
&UiGlobalTransform,
Has<InteractionDisabled>,
)>,
q_thumb: Query<&ComputedNode, With<CoreSliderThumb>>,
q_children: Query<&Children>,
q_parents: Query<&ChildOf>,
focus: Option<ResMut<InputFocus>>,
focus_visible: Option<ResMut<InputFocusVisible>>,
mut commands: Commands,
ui_scale: Res<UiScale>,
) {
if q_thumb.contains(trigger.target()) {
// Thumb click, stop propagation to prevent track click.
trigger.propagate(false);
// Find the slider entity that's an ancestor of the thumb
if let Some(slider_entity) = q_parents
.iter_ancestors(trigger.target())
.find(|entity| q_slider.contains(*entity))
{
// Set focus to slider and hide focus ring
if let Some(mut focus) = focus {
focus.0 = Some(slider_entity);
}
if let Some(mut focus_visible) = focus_visible {
focus_visible.0 = false;
}
}
} else if let Ok((slider, value, range, step, node, node_target, transform, disabled)) =
q_slider.get(trigger.target())
{
// Track click
trigger.propagate(false);
// Set focus to slider and hide focus ring
if let Some(mut focus) = focus {
focus.0 = (trigger.target() != Entity::PLACEHOLDER).then_some(trigger.target());
}
if let Some(mut focus_visible) = focus_visible {
focus_visible.0 = false;
}
if disabled {
return;
}
// Find thumb size by searching descendants for the first entity with CoreSliderThumb
let thumb_size = q_children
.iter_descendants(trigger.target())
.find_map(|child_id| q_thumb.get(child_id).ok().map(|thumb| thumb.size().x))
.unwrap_or(0.0);
// Detect track click.
let local_pos = transform.try_inverse().unwrap().transform_point2(
trigger.event().pointer_location.position * node_target.scale_factor() / ui_scale.0,
);
let track_width = node.size().x - thumb_size;
// Avoid division by zero
let click_val = if track_width > 0. {
local_pos.x * range.span() / track_width + range.center()
} else {
0.
};
// Compute new value from click position
let new_value = range.clamp(match slider.track_click {
TrackClick::Drag => {
return;
}
TrackClick::Step => {
if click_val < value.0 {
value.0 - step.0
} else {
value.0 + step.0
}
}
TrackClick::Snap => click_val,
});
if let Some(on_change) = slider.on_change {
commands.run_system_with(on_change, new_value);
} else {
commands
.entity(trigger.target())
.insert(SliderValue(new_value));
}
}
}
pub(crate) fn slider_on_drag_start(
mut trigger: On<Pointer<DragStart>>,
mut q_slider: Query<
(
&SliderValue,
&mut CoreSliderDragState,
Has<InteractionDisabled>,
),
With<CoreSlider>,
>,
) {
if let Ok((value, mut drag, disabled)) = q_slider.get_mut(trigger.target()) {
trigger.propagate(false);
if !disabled {
drag.dragging = true;
drag.offset = value.0;
}
}
}
pub(crate) fn slider_on_drag(
mut trigger: On<Pointer<Drag>>,
mut q_slider: Query<(
&ComputedNode,
&CoreSlider,
&SliderRange,
&UiGlobalTransform,
&mut CoreSliderDragState,
Has<InteractionDisabled>,
)>,
q_thumb: Query<&ComputedNode, With<CoreSliderThumb>>,
q_children: Query<&Children>,
mut commands: Commands,
ui_scale: Res<UiScale>,
) {
if let Ok((node, slider, range, transform, drag, disabled)) = q_slider.get_mut(trigger.target())
{
trigger.propagate(false);
if drag.dragging && !disabled {
let mut distance = trigger.event().distance / ui_scale.0;
distance.y *= -1.;
let distance = transform.transform_vector2(distance);
// Find thumb size by searching descendants for the first entity with CoreSliderThumb
let thumb_size = q_children
.iter_descendants(trigger.target())
.find_map(|child_id| q_thumb.get(child_id).ok().map(|thumb| thumb.size().x))
.unwrap_or(0.0);
let slider_width = ((node.size().x - thumb_size) * node.inverse_scale_factor).max(1.0);
let span = range.span();
let new_value = if span > 0. {
range.clamp(drag.offset + (distance.x * span) / slider_width)
} else {
range.start() + span * 0.5
};
if let Some(on_change) = slider.on_change {
commands.run_system_with(on_change, new_value);
} else {
commands
.entity(trigger.target())
.insert(SliderValue(new_value));
}
}
}
}
pub(crate) fn slider_on_drag_end(
mut trigger: On<Pointer<DragEnd>>,
mut q_slider: Query<(&CoreSlider, &mut CoreSliderDragState)>,
) {
if let Ok((_slider, mut drag)) = q_slider.get_mut(trigger.target()) {
trigger.propagate(false);
if drag.dragging {
drag.dragging = false;
}
}
}
fn slider_on_key_input(
mut trigger: On<FocusedInput<KeyboardInput>>,
q_slider: Query<(
&CoreSlider,
&SliderValue,
&SliderRange,
&SliderStep,
Has<InteractionDisabled>,
)>,
mut commands: Commands,
) {
if let Ok((slider, value, range, step, disabled)) = q_slider.get(trigger.target()) {
let event = &trigger.event().input;
if !disabled && event.state == ButtonState::Pressed {
let new_value = match event.key_code {
KeyCode::ArrowLeft => range.clamp(value.0 - step.0),
KeyCode::ArrowRight => range.clamp(value.0 + step.0),
KeyCode::Home => range.start(),
KeyCode::End => range.end(),
_ => {
return;
}
};
trigger.propagate(false);
if let Some(on_change) = slider.on_change {
commands.run_system_with(on_change, new_value);
} else {
commands
.entity(trigger.target())
.insert(SliderValue(new_value));
}
}
}
}
pub(crate) fn slider_on_insert(trigger: On<Insert, CoreSlider>, mut world: DeferredWorld) {
let mut entity = world.entity_mut(trigger.target());
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
accessibility.set_orientation(Orientation::Horizontal);
}
}
pub(crate) fn slider_on_insert_value(trigger: On<Insert, SliderValue>, mut world: DeferredWorld) {
let mut entity = world.entity_mut(trigger.target());
let value = entity.get::<SliderValue>().unwrap().0;
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
accessibility.set_numeric_value(value.into());
}
}
pub(crate) fn slider_on_insert_range(trigger: On<Insert, SliderRange>, mut world: DeferredWorld) {
let mut entity = world.entity_mut(trigger.target());
let range = *entity.get::<SliderRange>().unwrap();
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
accessibility.set_min_numeric_value(range.start().into());
accessibility.set_max_numeric_value(range.end().into());
}
}
pub(crate) fn slider_on_insert_step(trigger: On<Insert, SliderStep>, mut world: DeferredWorld) {
let mut entity = world.entity_mut(trigger.target());
let step = entity.get::<SliderStep>().unwrap().0;
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
accessibility.set_numeric_value_step(step.into());
}
}
/// An [`EntityEvent`] that can be triggered on a slider to modify its value (using the `on_change` callback).
/// This can be used to control the slider via gamepad buttons or other inputs. The value will be
/// clamped when the event is processed.
///
/// # Example:
///
/// ```
/// use bevy_ecs::system::Commands;
/// use bevy_core_widgets::{CoreSlider, SliderRange, SliderValue, SetSliderValue};
///
/// fn setup(mut commands: Commands) {
/// // Create a slider
/// let slider = commands.spawn((
/// CoreSlider::default(),
/// SliderValue(0.5),
/// SliderRange::new(0.0, 1.0),
/// )).id();
///
/// // Set to an absolute value
/// commands.trigger_targets(SetSliderValue::Absolute(0.75), slider);
///
/// // Adjust relatively
/// commands.trigger_targets(SetSliderValue::Relative(-0.25), slider);
/// }
/// ```
#[derive(Event, EntityEvent, Clone)]
pub enum SetSliderValue {
/// Set the slider value to a specific value.
Absolute(f32),
/// Add a delta to the slider value.
Relative(f32),
/// Add a delta to the slider value, multiplied by the step size.
RelativeStep(f32),
}
fn slider_on_set_value(
mut trigger: On<SetSliderValue>,
q_slider: Query<(&CoreSlider, &SliderValue, &SliderRange, Option<&SliderStep>)>,
mut commands: Commands,
) {
if let Ok((slider, value, range, step)) = q_slider.get(trigger.target()) {
trigger.propagate(false);
let new_value = match trigger.event() {
SetSliderValue::Absolute(new_value) => range.clamp(*new_value),
SetSliderValue::Relative(delta) => range.clamp(value.0 + *delta),
SetSliderValue::RelativeStep(delta) => {
range.clamp(value.0 + *delta * step.map(|s| s.0).unwrap_or_default())
}
};
if let Some(on_change) = slider.on_change {
commands.run_system_with(on_change, new_value);
} else {
commands
.entity(trigger.target())
.insert(SliderValue(new_value));
}
}
}
/// Plugin that adds the observers for the [`CoreSlider`] widget.
pub struct CoreSliderPlugin;
impl Plugin for CoreSliderPlugin {
fn build(&self, app: &mut App) {
app.add_observer(slider_on_pointer_down)
.add_observer(slider_on_drag_start)
.add_observer(slider_on_drag_end)
.add_observer(slider_on_drag)
.add_observer(slider_on_key_input)
.add_observer(slider_on_insert)
.add_observer(slider_on_insert_value)
.add_observer(slider_on_insert_range)
.add_observer(slider_on_insert_step)
.add_observer(slider_on_set_value);
}
}

View File

@ -0,0 +1,38 @@
//! 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.
// Note on naming: the `Core` prefix is used on components that would normally be internal to the
// styled/opinionated widgets that use them. Components which are directly exposed to users above
// the widget level, like `SliderValue`, should not have the `Core` prefix.
mod core_button;
mod core_checkbox;
mod core_slider;
use bevy_app::{App, Plugin};
pub use core_button::{CoreButton, CoreButtonPlugin};
pub use core_checkbox::{CoreCheckbox, CoreCheckboxPlugin, SetChecked, ToggleChecked};
pub use core_slider::{
CoreSlider, CoreSliderDragState, CoreSliderPlugin, CoreSliderThumb, SetSliderValue,
SliderRange, SliderStep, SliderValue, TrackClick,
};
/// 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, CoreCheckboxPlugin, CoreSliderPlugin));
}
}

View File

@ -31,7 +31,7 @@ bevy_state = { path = "../bevy_state", version = "0.16.0-dev" }
# other
serde = { version = "1.0", features = ["derive"], optional = true }
ron = { version = "0.8.0", optional = true }
ron = { version = "0.10", optional = true }
tracing = { version = "0.1", default-features = false, features = ["std"] }
[lints]

View File

@ -49,7 +49,7 @@ pub enum CiTestingEvent {
}
/// A custom event that can be configured from a configuration file for CI testing.
#[derive(Event)]
#[derive(Event, BufferedEvent)]
pub struct CiTestingCustomEvent(pub String);
#[cfg(test)]

View File

@ -5,9 +5,9 @@ use bevy_color::prelude::*;
use bevy_ecs::prelude::*;
use bevy_picking::backend::HitData;
use bevy_picking::hover::HoverMap;
use bevy_picking::pointer::{Location, PointerId, PointerPress};
use bevy_picking::pointer::{Location, PointerId, PointerInput, PointerLocation, PointerPress};
use bevy_picking::prelude::*;
use bevy_picking::{pointer, PickingSystems};
use bevy_picking::PickingSystems;
use bevy_reflect::prelude::*;
use bevy_render::prelude::*;
use bevy_text::prelude::*;
@ -91,11 +91,11 @@ impl Plugin for DebugPickingPlugin {
(
// This leaves room to easily change the log-level associated
// with different events, should that be desired.
log_event_debug::<pointer::PointerInput>.run_if(DebugPickingMode::is_noisy),
log_event_debug::<PointerInput>.run_if(DebugPickingMode::is_noisy),
log_pointer_event_debug::<Over>,
log_pointer_event_debug::<Out>,
log_pointer_event_debug::<Pressed>,
log_pointer_event_debug::<Released>,
log_pointer_event_debug::<Press>,
log_pointer_event_debug::<Release>,
log_pointer_event_debug::<Click>,
log_pointer_event_trace::<Move>.run_if(DebugPickingMode::is_noisy),
log_pointer_event_debug::<DragStart>,
@ -121,7 +121,7 @@ impl Plugin for DebugPickingPlugin {
}
/// Listen for any event and logs it at the debug level
pub fn log_event_debug<E: Event + Debug>(mut events: EventReader<pointer::PointerInput>) {
pub fn log_event_debug<E: BufferedEvent + Debug>(mut events: EventReader<PointerInput>) {
for event in events.read() {
debug!("{event:?}");
}
@ -214,7 +214,7 @@ pub fn update_debug_data(
entity_names: Query<NameOrEntity>,
mut pointers: Query<(
&PointerId,
&pointer::PointerLocation,
&PointerLocation,
&PointerPress,
&mut PointerDebug,
)>,

View File

@ -36,7 +36,7 @@ backtrace = ["std"]
## Enables `tracing` integration, allowing spans and other metrics to be reported
## through that framework.
trace = ["std", "dep:tracing"]
trace = ["std", "dep:tracing", "bevy_utils/debug"]
## Enables a more detailed set of traces which may be noisy if left on by default.
detailed_trace = ["trace"]
@ -64,9 +64,9 @@ std = [
"bevy_reflect?/std",
"bevy_tasks/std",
"bevy_utils/parallel",
"bevy_utils/std",
"bitflags/std",
"concurrent-queue/std",
"disqualified/alloc",
"fixedbitset/std",
"indexmap/std",
"serde?/std",
@ -99,7 +99,6 @@ bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-fea
] }
bitflags = { version = "2.3", default-features = false }
disqualified = { version = "1.0", default-features = false }
fixedbitset = { version = "0.5", default-features = false }
serde = { version = "1", default-features = false, features = [
"alloc",

View File

@ -277,26 +277,24 @@ world.spawn(PlayerBundle {
});
```
### Events
### Buffered Events
Events offer a communication channel between one or more systems. Events can be sent using the system parameter `EventWriter` and received with `EventReader`.
Buffered events offer a communication channel between one or more systems.
They can be sent using the `EventWriter` system parameter and received with `EventReader`.
```rust
use bevy_ecs::prelude::*;
#[derive(Event)]
struct MyEvent {
message: String,
#[derive(Event, BufferedEvent)]
struct Message(String);
fn writer(mut writer: EventWriter<Message>) {
writer.write(Message("Hello!".to_string()));
}
fn writer(mut writer: EventWriter<MyEvent>) {
writer.write(MyEvent {
message: "hello!".to_string(),
});
}
fn reader(mut reader: EventReader<MyEvent>) {
for event in reader.read() {
fn reader(mut reader: EventReader<Message>) {
for Message(message) in reader.read() {
println!("{}", message);
}
}
```
@ -309,37 +307,39 @@ Observers are systems that listen for a "trigger" of a specific `Event`:
use bevy_ecs::prelude::*;
#[derive(Event)]
struct MyEvent {
struct Speak {
message: String
}
let mut world = World::new();
world.add_observer(|trigger: Trigger<MyEvent>| {
println!("{}", trigger.event().message);
world.add_observer(|trigger: On<Speak>| {
println!("{}", trigger.message);
});
world.flush();
world.trigger(MyEvent {
message: "hello!".to_string(),
world.trigger(Speak {
message: "Hello!".to_string(),
});
```
These differ from `EventReader` and `EventWriter` in that they are "reactive". Rather than happening at a specific point in a schedule, they happen _immediately_ whenever a trigger happens. Triggers can trigger other triggers, and they all will be evaluated at the same time!
These differ from `EventReader` and `EventWriter` in that they are "reactive".
Rather than happening at a specific point in a schedule, they happen _immediately_ whenever a trigger happens.
Triggers can trigger other triggers, and they all will be evaluated at the same time!
Events can also be triggered to target specific entities:
If the event is an `EntityEvent`, it can also be triggered to target specific entities:
```rust
use bevy_ecs::prelude::*;
#[derive(Event)]
#[derive(Event, EntityEvent)]
struct Explode;
let mut world = World::new();
let entity = world.spawn_empty().id();
world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
println!("Entity {} goes BOOM!", trigger.target());
commands.entity(trigger.target()).despawn();
});

View File

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

View File

@ -1,4 +1,4 @@
//! In this example a system sends a custom event with a 50/50 chance during any frame.
//! In this example a system sends a custom buffered event with a 50/50 chance during any frame.
//! If an event was sent, it will be printed by the console in a receiving system.
#![expect(clippy::print_stdout, reason = "Allowed in examples.")]
@ -15,7 +15,7 @@ fn main() {
// Create a schedule to store our systems
let mut schedule = Schedule::default();
// Events need to be updated in every frame in order to clear our buffers.
// Buffered events need to be updated every frame in order to clear our buffers.
// This update should happen before we use the events.
// Here, we use system sets to control the ordering.
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
@ -37,7 +37,7 @@ fn main() {
}
// This is our event that we will send and receive in systems
#[derive(Event)]
#[derive(Event, BufferedEvent)]
struct MyEvent {
pub message: String,
pub random_value: f32,

View File

@ -13,11 +13,28 @@ use syn::{
LitStr, Member, Path, Result, Token, Type, Visibility,
};
pub const EVENT: &str = "event";
pub const EVENT: &str = "entity_event";
pub const AUTO_PROPAGATE: &str = "auto_propagate";
pub const TRAVERSAL: &str = "traversal";
pub fn derive_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {}
})
}
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let mut auto_propagate = false;
let mut traversal: Type = parse_quote!(());
@ -49,13 +66,30 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
impl #impl_generics #bevy_ecs_path::event::EntityEvent for #struct_name #type_generics #where_clause {
type Traversal = #traversal;
const AUTO_PROPAGATE: bool = #auto_propagate;
}
})
}
pub fn derive_buffered_event(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::BufferedEvent for #struct_name #type_generics #where_clause {}
})
}
pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let bevy_ecs_path: Path = crate::bevy_ecs_path();
@ -434,7 +468,7 @@ impl HookAttributeKind {
HookAttributeKind::Path(path) => path.to_token_stream(),
HookAttributeKind::Call(call) => {
quote!({
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::component::HookContext) {
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {
(#call)(world, ctx)
}
_internal_hook
@ -658,7 +692,7 @@ fn hook_register_function_call(
) -> Option<TokenStream2> {
function.map(|meta| {
quote! {
fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> {
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
::core::option::Option::Some(#meta)
}
}

View File

@ -1,4 +1,5 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
//! Macros for deriving ECS traits.
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
extern crate proc_macro;
@ -15,7 +16,7 @@ use crate::{
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{format_ident, quote};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam,
@ -28,12 +29,48 @@ enum BundleFieldKind {
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
const BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS: &str = "ignore_from_components";
#[derive(Debug)]
struct BundleAttributes {
impl_from_components: bool,
}
impl Default for BundleAttributes {
fn default() -> Self {
Self {
impl_from_components: true,
}
}
}
/// Implement the `Bundle` trait.
#[proc_macro_derive(Bundle, attributes(bundle))]
pub fn derive_bundle(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = bevy_ecs_path();
let mut errors = vec![];
let mut attributes = BundleAttributes::default();
for attr in &ast.attrs {
if attr.path().is_ident(BUNDLE_ATTRIBUTE_NAME) {
let parsing = attr.parse_nested_meta(|meta| {
if meta.path.is_ident(BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS) {
attributes.impl_from_components = false;
return Ok(());
}
Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`")))
});
if let Err(error) = parsing {
errors.push(error.into_compile_error());
}
}
}
let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
Ok(fields) => fields,
Err(e) => return e.into_compile_error().into(),
@ -42,6 +79,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
let mut field_kind = Vec::with_capacity(named_fields.len());
for field in named_fields {
let mut kind = BundleFieldKind::Component;
for attr in field
.attrs
.iter()
@ -49,7 +88,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
{
if let Err(error) = attr.parse_nested_meta(|meta| {
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
field_kind.push(BundleFieldKind::Ignore);
kind = BundleFieldKind::Ignore;
Ok(())
} else {
Err(meta.error(format!(
@ -61,7 +100,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}
}
field_kind.push(BundleFieldKind::Component);
field_kind.push(kind);
}
let field = named_fields
@ -74,61 +113,33 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
.map(|field| &field.ty)
.collect::<Vec<_>>();
let mut field_component_ids = Vec::new();
let mut field_get_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
let mut field_required_components = Vec::new();
let mut active_field_types = Vec::new();
let mut active_field_tokens = Vec::new();
let mut inactive_field_tokens = Vec::new();
for (((i, field_type), field_kind), field) in field_type
.iter()
.enumerate()
.zip(field_kind.iter())
.zip(field.iter())
{
let field_tokens = match field {
Some(field) => field.to_token_stream(),
None => Index::from(i).to_token_stream(),
};
match field_kind {
BundleFieldKind::Component => {
field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, &mut *ids);
});
field_required_components.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);
});
field_get_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
});
match field {
Some(field) => {
field_get_components.push(quote! {
self.#field.get_components(&mut *func);
});
field_from_components.push(quote! {
#field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
});
}
None => {
let index = Index::from(i);
field_get_components.push(quote! {
self.#index.get_components(&mut *func);
});
field_from_components.push(quote! {
#index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
});
}
}
active_field_types.push(field_type);
active_field_tokens.push(field_tokens);
}
BundleFieldKind::Ignore => {
field_from_components.push(quote! {
#field: ::core::default::Default::default(),
});
}
BundleFieldKind::Ignore => inactive_field_tokens.push(field_tokens),
}
}
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let struct_name = &ast.ident;
TokenStream::from(quote! {
let bundle_impl = quote! {
// SAFETY:
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
@ -138,40 +149,27 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
fn component_ids(
components: &mut #ecs_path::component::ComponentsRegistrator,
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
){
#(#field_component_ids)*
) {
#(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(components, ids);)*
}
fn get_component_ids(
components: &#ecs_path::component::Components,
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
){
#(#field_get_component_ids)*
) {
#(<#active_field_types as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);)*
}
fn register_required_components(
components: &mut #ecs_path::component::ComponentsRegistrator,
required_components: &mut #ecs_path::component::RequiredComponents
){
#(#field_required_components)*
}
}
// 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)*
}
) {
#(<#active_field_types as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);)*
}
}
};
let dynamic_bundle_impl = quote! {
#[allow(deprecated)]
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
type Effect = ();
@ -181,12 +179,40 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
self,
func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
) {
#(#field_get_components)*
#(<#active_field_types as #ecs_path::bundle::DynamicBundle>::get_components(self.#active_field_tokens, &mut *func);)*
}
}
};
let from_components_impl = 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 {
#(#active_field_tokens: <#active_field_types as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),)*
#(#inactive_field_tokens: ::core::default::Default::default(),)*
}
}
}
});
let attribute_errors = &errors;
TokenStream::from(quote! {
#(#attribute_errors)*
#bundle_impl
#from_components_impl
#dynamic_bundle_impl
})
}
/// Implement the `MapEntities` trait.
#[proc_macro_derive(MapEntities, attributes(entities))]
pub fn derive_map_entities(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
@ -394,10 +420,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
> #path::system::SystemParamBuilder<#generic_struct> for #builder_name<#(#builder_type_parameters,)*>
#where_clause
{
fn build(self, world: &mut #path::world::World, meta: &mut #path::system::SystemMeta) -> <#generic_struct as #path::system::SystemParam>::State {
fn build(self, world: &mut #path::world::World) -> <#generic_struct as #path::system::SystemParam>::State {
let #builder_name { #(#fields: #field_locals,)* } = self;
#state_struct_name {
state: #path::system::SystemParamBuilder::build((#(#tuple_patterns,)*), world, meta)
state: #path::system::SystemParamBuilder::build((#(#tuple_patterns,)*), world)
}
}
}
@ -426,12 +452,16 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
type State = #state_struct_name<#punctuated_generic_idents>;
type Item<'w, 's> = #struct_name #ty_generics;
fn init_state(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self::State {
fn init_state(world: &mut #path::world::World) -> Self::State {
#state_struct_name {
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world, system_meta),
state: <#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_state(world),
}
}
fn init_access(state: &Self::State, system_meta: &mut #path::system::SystemMeta, component_access_set: &mut #path::query::FilteredAccessSet<#path::component::ComponentId>, world: &mut #path::world::World) {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::init_access(&state.state, system_meta, component_access_set, world);
}
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
}
@ -522,16 +552,31 @@ pub(crate) fn bevy_ecs_path() -> syn::Path {
BevyManifest::shared().get_path("bevy_ecs")
}
#[proc_macro_derive(Event, attributes(event))]
/// Implement the `Event` trait.
#[proc_macro_derive(Event)]
pub fn derive_event(input: TokenStream) -> TokenStream {
component::derive_event(input)
}
/// Implement the `EntityEvent` trait.
#[proc_macro_derive(EntityEvent, attributes(entity_event))]
pub fn derive_entity_event(input: TokenStream) -> TokenStream {
component::derive_entity_event(input)
}
/// Implement the `BufferedEvent` trait.
#[proc_macro_derive(BufferedEvent)]
pub fn derive_buffered_event(input: TokenStream) -> TokenStream {
component::derive_buffered_event(input)
}
/// Implement the `Resource` trait.
#[proc_macro_derive(Resource)]
pub fn derive_resource(input: TokenStream) -> TokenStream {
component::derive_resource(input)
}
/// Implement the `Component` trait.
#[proc_macro_derive(
Component,
attributes(component, require, relationship, relationship_target, entities)
@ -540,6 +585,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
}
/// Implement the `FromWorld` trait.
#[proc_macro_derive(FromWorld, attributes(from_world))]
pub fn derive_from_world(input: TokenStream) -> TokenStream {
let bevy_ecs_path = bevy_ecs_path();

View File

@ -74,12 +74,23 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
let user_generics = ast.generics.clone();
let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl();
let user_generics_with_world = {
let mut generics = ast.generics;
let mut generics = ast.generics.clone();
generics.params.insert(0, parse_quote!('__w));
generics
};
let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) =
user_generics_with_world.split_for_impl();
let user_generics_with_world_and_state = {
let mut generics = ast.generics;
generics.params.insert(0, parse_quote!('__w));
generics.params.insert(1, parse_quote!('__s));
generics
};
let (
user_impl_generics_with_world_and_state,
user_ty_generics_with_world_and_state,
user_where_clauses_with_world_and_state,
) = user_generics_with_world_and_state.split_for_impl();
let struct_name = ast.ident;
let read_only_struct_name = if attributes.is_mutable {
@ -164,13 +175,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
&visibility,
&item_struct_name,
&field_types,
&user_impl_generics_with_world,
&user_impl_generics_with_world_and_state,
&field_attrs,
&field_visibilities,
&field_idents,
&user_ty_generics,
&user_ty_generics_with_world,
user_where_clauses_with_world,
&user_ty_generics_with_world_and_state,
user_where_clauses_with_world_and_state,
);
let mutable_world_query_impl = world_query_impl(
&path,
@ -199,13 +210,13 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
&visibility,
&read_only_item_struct_name,
&read_only_field_types,
&user_impl_generics_with_world,
&user_impl_generics_with_world_and_state,
&field_attrs,
&field_visibilities,
&field_idents,
&user_ty_generics,
&user_ty_generics_with_world,
user_where_clauses_with_world,
&user_ty_generics_with_world_and_state,
user_where_clauses_with_world_and_state,
);
let readonly_world_query_impl = world_query_impl(
&path,
@ -256,11 +267,11 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
for #read_only_struct_name #user_ty_generics #user_where_clauses {
const IS_READ_ONLY: bool = true;
type ReadOnly = #read_only_struct_name #user_ty_generics;
type Item<'__w> = #read_only_item_struct_name #user_ty_generics_with_world;
type Item<'__w, '__s> = #read_only_item_struct_name #user_ty_generics_with_world_and_state;
fn shrink<'__wlong: '__wshort, '__wshort>(
item: Self::Item<'__wlong>
) -> Self::Item<'__wshort> {
fn shrink<'__wlong: '__wshort, '__wshort, '__s>(
item: Self::Item<'__wlong, '__s>
) -> Self::Item<'__wshort, '__s> {
#read_only_item_struct_name {
#(
#field_idents: <#read_only_field_types>::shrink(item.#field_idents),
@ -278,13 +289,26 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
#[inline(always)]
unsafe fn fetch<'__w>(
unsafe fn fetch<'__w, '__s>(
_state: &'__s Self::State,
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: #path::storage::TableRow,
) -> Self::Item<'__w> {
) -> Self::Item<'__w, '__s> {
Self::Item {
#(#field_idents: <#read_only_field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)*
#(#field_idents: <#read_only_field_types>::fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row),)*
}
}
}
impl #user_impl_generics #path::query::ReleaseStateQueryData
for #read_only_struct_name #user_ty_generics #user_where_clauses
// Make these HRTBs with an unused lifetime parameter to allow trivial constraints
// See https://github.com/rust-lang/rust/issues/48214
where #(for<'__a> #field_types: #path::query::QueryData<ReadOnly: #path::query::ReleaseStateQueryData>,)* {
fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> {
Self::Item {
#(#field_idents: <#read_only_field_types>::release_state(_item.#field_idents),)*
}
}
}
@ -301,11 +325,11 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
for #struct_name #user_ty_generics #user_where_clauses {
const IS_READ_ONLY: bool = #is_read_only;
type ReadOnly = #read_only_struct_name #user_ty_generics;
type Item<'__w> = #item_struct_name #user_ty_generics_with_world;
type Item<'__w, '__s> = #item_struct_name #user_ty_generics_with_world_and_state;
fn shrink<'__wlong: '__wshort, '__wshort>(
item: Self::Item<'__wlong>
) -> Self::Item<'__wshort> {
fn shrink<'__wlong: '__wshort, '__wshort, '__s>(
item: Self::Item<'__wlong, '__s>
) -> Self::Item<'__wshort, '__s> {
#item_struct_name {
#(
#field_idents: <#field_types>::shrink(item.#field_idents),
@ -323,13 +347,26 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
#[inline(always)]
unsafe fn fetch<'__w>(
unsafe fn fetch<'__w, '__s>(
_state: &'__s Self::State,
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: #path::storage::TableRow,
) -> Self::Item<'__w> {
) -> Self::Item<'__w, '__s> {
Self::Item {
#(#field_idents: <#field_types>::fetch(&mut _fetch.#named_field_idents, _entity, _table_row),)*
#(#field_idents: <#field_types>::fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row),)*
}
}
}
impl #user_impl_generics #path::query::ReleaseStateQueryData
for #struct_name #user_ty_generics #user_where_clauses
// Make these HRTBs with an unused lifetime parameter to allow trivial constraints
// See https://github.com/rust-lang/rust/issues/48214
where #(for<'__a> #field_types: #path::query::ReleaseStateQueryData,)* {
fn release_state<'__w>(_item: Self::Item<'__w, '_>) -> Self::Item<'__w, 'static> {
Self::Item {
#(#field_idents: <#field_types>::release_state(_item.#field_idents),)*
}
}
}

View File

@ -102,11 +102,12 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream {
#[allow(unused_variables)]
#[inline(always)]
unsafe fn filter_fetch<'__w>(
_state: &Self::State,
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: #path::storage::TableRow,
) -> bool {
true #(&& <#field_types>::filter_fetch(&mut _fetch.#named_field_idents, _entity, _table_row))*
true #(&& <#field_types>::filter_fetch(&_state.#named_field_idents, &mut _fetch.#named_field_idents, _entity, _table_row))*
}
}
};

View File

@ -10,13 +10,13 @@ pub(crate) fn item_struct(
visibility: &Visibility,
item_struct_name: &Ident,
field_types: &Vec<proc_macro2::TokenStream>,
user_impl_generics_with_world: &ImplGenerics,
user_impl_generics_with_world_and_state: &ImplGenerics,
field_attrs: &Vec<Vec<Attribute>>,
field_visibilities: &Vec<Visibility>,
field_idents: &Vec<proc_macro2::TokenStream>,
user_ty_generics: &TypeGenerics,
user_ty_generics_with_world: &TypeGenerics,
user_where_clauses_with_world: Option<&WhereClause>,
user_ty_generics_with_world_and_state: &TypeGenerics,
user_where_clauses_with_world_and_state: Option<&WhereClause>,
) -> proc_macro2::TokenStream {
let item_attrs = quote! {
#[doc = concat!(
@ -33,20 +33,20 @@ pub(crate) fn item_struct(
Fields::Named(_) => quote! {
#derive_macro_call
#item_attrs
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w>,)*
#visibility struct #item_struct_name #user_impl_generics_with_world_and_state #user_where_clauses_with_world_and_state {
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::QueryData>::Item<'__w, '__s>,)*
}
},
Fields::Unnamed(_) => quote! {
#derive_macro_call
#item_attrs
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world(
#( #field_visibilities <#field_types as #path::query::QueryData>::Item<'__w>, )*
#visibility struct #item_struct_name #user_impl_generics_with_world_and_state #user_where_clauses_with_world_and_state(
#( #field_visibilities <#field_types as #path::query::QueryData>::Item<'__w, '__s>, )*
);
},
Fields::Unit => quote! {
#item_attrs
#visibility type #item_struct_name #user_ty_generics_with_world = #struct_name #user_ty_generics;
#visibility type #item_struct_name #user_ty_generics_with_world_and_state = #struct_name #user_ty_generics;
},
}
}
@ -79,7 +79,7 @@ pub(crate) fn world_query_impl(
#[automatically_derived]
#visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
#(#named_field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)*
#marker_name: &'__w (),
#marker_name: &'__w(),
}
impl #user_impl_generics_with_world Clone for #fetch_struct_name #user_ty_generics_with_world
@ -110,9 +110,9 @@ pub(crate) fn world_query_impl(
}
}
unsafe fn init_fetch<'__w>(
unsafe fn init_fetch<'__w, '__s>(
_world: #path::world::unsafe_world_cell::UnsafeWorldCell<'__w>,
state: &Self::State,
state: &'__s Self::State,
_last_run: #path::component::Tick,
_this_run: #path::component::Tick,
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
@ -133,9 +133,9 @@ pub(crate) fn world_query_impl(
/// SAFETY: we call `set_archetype` for each member that implements `Fetch`
#[inline]
unsafe fn set_archetype<'__w>(
unsafe fn set_archetype<'__w, '__s>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_state: &Self::State,
_state: &'__s Self::State,
_archetype: &'__w #path::archetype::Archetype,
_table: &'__w #path::storage::Table
) {
@ -144,9 +144,9 @@ pub(crate) fn world_query_impl(
/// SAFETY: we call `set_table` for each member that implements `Fetch`
#[inline]
unsafe fn set_table<'__w>(
unsafe fn set_table<'__w, '__s>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_state: &Self::State,
_state: &'__s Self::State,
_table: &'__w #path::storage::Table
) {
#(<#field_types>::set_table(&mut _fetch.#named_field_idents, &_state.#named_field_idents, _table);)*

View File

@ -691,41 +691,41 @@ impl Archetype {
self.flags().contains(ArchetypeFlags::ON_DESPAWN_HOOK)
}
/// 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 [`Add`] observer
///
/// [`OnAdd`]: crate::world::OnAdd
/// [`Add`]: crate::lifecycle::Add
#[inline]
pub fn has_add_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
}
/// 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 [`Insert`] observer
///
/// [`OnInsert`]: crate::world::OnInsert
/// [`Insert`]: crate::lifecycle::Insert
#[inline]
pub fn has_insert_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
}
/// 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 [`Replace`] observer
///
/// [`OnReplace`]: crate::world::OnReplace
/// [`Replace`]: crate::lifecycle::Replace
#[inline]
pub fn has_replace_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER)
}
/// 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 [`Remove`] observer
///
/// [`OnRemove`]: crate::world::OnRemove
/// [`Remove`]: crate::lifecycle::Remove
#[inline]
pub fn has_remove_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
}
/// 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 [`Despawn`] observer
///
/// [`OnDespawn`]: crate::world::OnDespawn
/// [`Despawn`]: crate::lifecycle::Despawn
#[inline]
pub fn has_despawn_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER)

View File

@ -2,6 +2,57 @@
//!
//! This module contains the [`Bundle`] trait and some other helper types.
/// Derive the [`Bundle`] trait
///
/// You can apply this derive macro to structs that are
/// composed of [`Component`]s or
/// other [`Bundle`]s.
///
/// ## Attributes
///
/// Sometimes parts of the Bundle should not be inserted.
/// Those can be marked with `#[bundle(ignore)]`, and they will be skipped.
/// In that case, the field needs to implement [`Default`] unless you also ignore
/// the [`BundleFromComponents`] implementation.
///
/// ```rust
/// # use bevy_ecs::prelude::{Component, Bundle};
/// # #[derive(Component)]
/// # struct Hitpoint;
/// #
/// #[derive(Bundle)]
/// struct HitpointMarker {
/// hitpoints: Hitpoint,
///
/// #[bundle(ignore)]
/// creator: Option<String>
/// }
/// ```
///
/// Some fields may be bundles that do not implement
/// [`BundleFromComponents`]. This happens for bundles that cannot be extracted.
/// For example with [`SpawnRelatedBundle`](bevy_ecs::spawn::SpawnRelatedBundle), see below for an
/// example usage.
/// In those cases you can either ignore it as above,
/// or you can opt out the whole Struct by marking it as ignored with
/// `#[bundle(ignore_from_components)]`.
///
/// ```rust
/// # use bevy_ecs::prelude::{Component, Bundle, ChildOf, Spawn};
/// # #[derive(Component)]
/// # struct Hitpoint;
/// # #[derive(Component)]
/// # struct Marker;
/// #
/// use bevy_ecs::spawn::SpawnRelatedBundle;
///
/// #[derive(Bundle)]
/// #[bundle(ignore_from_components)]
/// struct HitpointMarker {
/// hitpoints: Hitpoint,
/// related_spawner: SpawnRelatedBundle<ChildOf, Spawn<Marker>>,
/// }
/// ```
pub use bevy_ecs_macros::Bundle;
use crate::{
@ -15,15 +66,13 @@ use crate::{
RequiredComponents, StorageType, Tick,
},
entity::{Entities, Entity, EntityLocation},
lifecycle::{ADD, INSERT, REMOVE, REPLACE},
observer::Observers,
prelude::World,
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::{
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
ON_REPLACE,
},
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut},
};
use alloc::{boxed::Box, vec, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
@ -501,10 +550,9 @@ impl BundleInfo {
// SAFETY: the caller ensures component_id is valid.
unsafe { components.get_info_unchecked(id).name() }
})
.collect::<Vec<_>>()
.join(", ");
.collect::<Vec<_>>();
panic!("Bundle {bundle_type_name} has duplicate components: {names}");
panic!("Bundle {bundle_type_name} has duplicate components: {names:?}");
}
// handle explicit components
@ -1142,8 +1190,8 @@ impl<'w> BundleInserter<'w> {
if insert_mode == InsertMode::Replace {
if archetype.has_replace_observer() {
deferred_world.trigger_observers(
ON_REPLACE,
entity,
REPLACE,
Some(entity),
archetype_after_insert.iter_existing(),
caller,
);
@ -1327,8 +1375,8 @@ impl<'w> BundleInserter<'w> {
);
if new_archetype.has_add_observer() {
deferred_world.trigger_observers(
ON_ADD,
entity,
ADD,
Some(entity),
archetype_after_insert.iter_added(),
caller,
);
@ -1345,8 +1393,8 @@ impl<'w> BundleInserter<'w> {
);
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(
ON_INSERT,
entity,
INSERT,
Some(entity),
archetype_after_insert.iter_inserted(),
caller,
);
@ -1364,8 +1412,8 @@ impl<'w> BundleInserter<'w> {
);
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(
ON_INSERT,
entity,
INSERT,
Some(entity),
archetype_after_insert.iter_added(),
caller,
);
@ -1518,8 +1566,8 @@ impl<'w> BundleRemover<'w> {
};
if self.old_archetype.as_ref().has_replace_observer() {
deferred_world.trigger_observers(
ON_REPLACE,
entity,
REPLACE,
Some(entity),
bundle_components_in_archetype(),
caller,
);
@ -1533,8 +1581,8 @@ impl<'w> BundleRemover<'w> {
);
if self.old_archetype.as_ref().has_remove_observer() {
deferred_world.trigger_observers(
ON_REMOVE,
entity,
REMOVE,
Some(entity),
bundle_components_in_archetype(),
caller,
);
@ -1784,8 +1832,8 @@ impl<'w> BundleSpawner<'w> {
);
if archetype.has_add_observer() {
deferred_world.trigger_observers(
ON_ADD,
entity,
ADD,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
@ -1799,8 +1847,8 @@ impl<'w> BundleSpawner<'w> {
);
if archetype.has_insert_observer() {
deferred_world.trigger_observers(
ON_INSERT,
entity,
INSERT,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
@ -2072,7 +2120,7 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
#[cfg(test)]
mod tests {
use crate::{
archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld,
archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld,
};
use alloc::vec;
@ -2122,6 +2170,26 @@ mod tests {
}
}
#[derive(Bundle)]
#[bundle(ignore_from_components)]
struct BundleNoExtract {
b: B,
no_from_comp: crate::spawn::SpawnRelatedBundle<ChildOf, Spawn<C>>,
}
#[test]
fn can_spawn_bundle_without_extract() {
let mut world = World::new();
let id = world
.spawn(BundleNoExtract {
b: B,
no_from_comp: Children::spawn(Spawn(C)),
})
.id();
assert!(world.entity(id).get::<Children>().is_some());
}
#[test]
fn component_hook_order_spawn_despawn() {
let mut world = World::new();
@ -2317,7 +2385,7 @@ mod tests {
#[derive(Resource, Default)]
struct Count(u32);
world.init_resource::<Count>();
world.add_observer(|_t: Trigger<ArchetypeCreated>, mut count: ResMut<Count>| {
world.add_observer(|_t: On<ArchetypeCreated>, mut count: ResMut<Count>| {
count.0 += 1;
});
@ -2329,4 +2397,13 @@ mod tests {
assert_eq!(world.resource::<Count>().0, 3);
}
#[derive(Bundle)]
#[expect(unused, reason = "tests the output of the derive macro is valid")]
struct Ignore {
#[bundle(ignore)]
foo: i32,
#[bundle(ignore)]
bar: i32,
}
}

View File

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

View File

@ -5,16 +5,17 @@ use crate::{
bundle::BundleInfo,
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
lifecycle::{ComponentHook, ComponentHooks},
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
resource::Resource,
storage::{SparseSetIndex, SparseSets, Table, TableRow},
system::{Local, SystemParam},
world::{DeferredWorld, FromWorld, World},
world::{FromWorld, World},
};
use alloc::boxed::Box;
use alloc::{borrow::Cow, format, vec::Vec};
pub use bevy_ecs_macros::Component;
use bevy_ecs_macros::Event;
use bevy_platform::sync::Arc;
use bevy_platform::{
collections::{HashMap, HashSet},
@ -23,7 +24,7 @@ use bevy_platform::{
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bevy_utils::TypeIdMap;
use bevy_utils::{prelude::DebugName, TypeIdMap};
use core::{
alloc::Layout,
any::{Any, TypeId},
@ -33,7 +34,6 @@ use core::{
mem::needs_drop,
ops::{Deref, DerefMut},
};
use disqualified::ShortName;
use smallvec::SmallVec;
use thiserror::Error;
@ -375,7 +375,8 @@ use thiserror::Error;
/// - `#[component(on_remove = on_remove_function)]`
///
/// ```
/// # use bevy_ecs::component::{Component, HookContext};
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::lifecycle::HookContext;
/// # use bevy_ecs::world::DeferredWorld;
/// # use bevy_ecs::entity::Entity;
/// # use bevy_ecs::component::ComponentId;
@ -404,7 +405,8 @@ use thiserror::Error;
/// This also supports function calls that yield closures
///
/// ```
/// # use bevy_ecs::component::{Component, HookContext};
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::lifecycle::HookContext;
/// # use bevy_ecs::world::DeferredWorld;
/// #
/// #[derive(Component)]
@ -595,7 +597,7 @@ mod private {
/// `&mut ...`, created while inserted onto an entity.
/// In all other ways, they are identical to mutable components.
/// This restriction allows hooks to observe all changes made to an immutable
/// component, effectively turning the `OnInsert` and `OnReplace` hooks into a
/// component, effectively turning the `Insert` and `Replace` hooks into a
/// `OnMutate` hook.
/// This is not practical for mutable components, as the runtime cost of invoking
/// a hook for every exclusive reference created would be far too high.
@ -656,244 +658,6 @@ pub enum StorageType {
SparseSet,
}
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext);
/// Context provided to a [`ComponentHook`].
#[derive(Clone, Copy, Debug)]
pub struct HookContext {
/// The [`Entity`] this hook was invoked for.
pub entity: Entity,
/// The [`ComponentId`] this hook was invoked for.
pub component_id: ComponentId,
/// The caller location is `Some` if the `track_caller` feature is enabled.
pub caller: MaybeLocation,
/// Configures how relationship hooks will run
pub relationship_hook_mode: RelationshipHookMode,
}
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
///
/// Hooks are functions that run when a component is added, overwritten, or removed from an entity.
/// These are intended to be used for structural side effects that need to happen when a component is added or removed,
/// and are not intended for general-purpose logic.
///
/// For example, you might use a hook to update a cached index when a component is added,
/// to clean up resources when a component is removed,
/// or to keep hierarchical data structures across entities in sync.
///
/// This information is stored in the [`ComponentInfo`] of the associated component.
///
/// There is two ways of configuring hooks for a component:
/// 1. Defining the relevant hooks on the [`Component`] implementation
/// 2. Using the [`World::register_component_hooks`] method
///
/// # Example 2
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_platform::collections::HashSet;
///
/// #[derive(Component)]
/// struct MyTrackedComponent;
///
/// #[derive(Resource, Default)]
/// struct TrackedEntities(HashSet<Entity>);
///
/// let mut world = World::new();
/// world.init_resource::<TrackedEntities>();
///
/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
/// assert!(tracked_component_query.iter(&world).next().is_none());
///
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, context| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.insert(context.entity);
/// });
///
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
/// tracked_entities.0.remove(&context.entity);
/// });
///
/// let entity = world.spawn(MyTrackedComponent).id();
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(tracked_entities.0.contains(&entity));
///
/// world.despawn(entity);
/// let tracked_entities = world.resource::<TrackedEntities>();
/// assert!(!tracked_entities.0.contains(&entity));
/// ```
#[derive(Debug, Clone, Default)]
pub struct ComponentHooks {
pub(crate) on_add: Option<ComponentHook>,
pub(crate) on_insert: Option<ComponentHook>,
pub(crate) on_replace: Option<ComponentHook>,
pub(crate) on_remove: Option<ComponentHook>,
pub(crate) on_despawn: Option<ComponentHook>,
}
impl ComponentHooks {
pub(crate) fn update_from_component<C: Component + ?Sized>(&mut self) -> &mut Self {
if let Some(hook) = C::on_add() {
self.on_add(hook);
}
if let Some(hook) = C::on_insert() {
self.on_insert(hook);
}
if let Some(hook) = C::on_replace() {
self.on_replace(hook);
}
if let Some(hook) = C::on_remove() {
self.on_remove(hook);
}
if let Some(hook) = C::on_despawn() {
self.on_despawn(hook);
}
self
}
/// Register a [`ComponentHook`] that will be run when this component is added to an entity.
/// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as
/// adding all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_add` hook
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_add(hook)
.expect("Component already has an on_add hook")
}
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
/// or replaced.
///
/// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_insert` hook
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_insert(hook)
.expect("Component already has an on_insert hook")
}
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
/// such as being replaced (with `.insert`) or removed.
///
/// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced,
/// allowing access to the previous data just before it is dropped.
/// This hook does *not* run if the entity did not already have this component.
///
/// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity).
///
/// # Warning
///
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
///
/// # Panics
///
/// Will panic if the component already has an `on_replace` hook
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_replace(hook)
.expect("Component already has an on_replace hook")
}
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
/// Despawning an entity counts as removing all of its components.
///
/// # Panics
///
/// Will panic if the component already has an `on_remove` hook
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_remove(hook)
.expect("Component already has an on_remove hook")
}
/// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
///
/// # Panics
///
/// Will panic if the component already has an `on_despawn` hook
pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self {
self.try_on_despawn(hook)
.expect("Component already has an on_despawn hook")
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
///
/// This is a fallible version of [`Self::on_add`].
///
/// Returns `None` if the component already has an `on_add` hook.
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_add.is_some() {
return None;
}
self.on_add = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
///
/// This is a fallible version of [`Self::on_insert`].
///
/// Returns `None` if the component already has an `on_insert` hook.
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_insert.is_some() {
return None;
}
self.on_insert = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed
///
/// This is a fallible version of [`Self::on_replace`].
///
/// Returns `None` if the component already has an `on_replace` hook.
pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_replace.is_some() {
return None;
}
self.on_replace = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity.
///
/// This is a fallible version of [`Self::on_remove`].
///
/// Returns `None` if the component already has an `on_remove` hook.
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_remove.is_some() {
return None;
}
self.on_remove = Some(hook);
Some(self)
}
/// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
///
/// This is a fallible version of [`Self::on_despawn`].
///
/// Returns `None` if the component already has an `on_despawn` hook.
pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> {
if self.on_despawn.is_some() {
return None;
}
self.on_despawn = Some(hook);
Some(self)
}
}
/// Stores metadata for a type of component or resource stored in a specific [`World`].
#[derive(Debug, Clone)]
pub struct ComponentInfo {
@ -913,8 +677,8 @@ impl ComponentInfo {
/// Returns the name of the current component.
#[inline]
pub fn name(&self) -> &str {
&self.descriptor.name
pub fn name(&self) -> DebugName {
self.descriptor.name.clone()
}
/// Returns `true` if the current component is mutable.
@ -1071,7 +835,7 @@ impl SparseSetIndex for ComponentId {
/// A value describing a component or resource, which may or may not correspond to a Rust type.
#[derive(Clone)]
pub struct ComponentDescriptor {
name: Cow<'static, str>,
name: DebugName,
// SAFETY: This must remain private. It must match the statically known StorageType of the
// associated rust component type if one exists.
storage_type: StorageType,
@ -1117,7 +881,7 @@ impl ComponentDescriptor {
/// Create a new `ComponentDescriptor` for the type `T`.
pub fn new<T: Component>() -> Self {
Self {
name: Cow::Borrowed(core::any::type_name::<T>()),
name: DebugName::type_name::<T>(),
storage_type: T::STORAGE_TYPE,
is_send_and_sync: true,
type_id: Some(TypeId::of::<T>()),
@ -1142,7 +906,7 @@ impl ComponentDescriptor {
clone_behavior: ComponentCloneBehavior,
) -> Self {
Self {
name: name.into(),
name: name.into().into(),
storage_type,
is_send_and_sync: true,
type_id: None,
@ -1158,7 +922,7 @@ impl ComponentDescriptor {
/// The [`StorageType`] for resources is always [`StorageType::Table`].
pub fn new_resource<T: Resource>() -> Self {
Self {
name: Cow::Borrowed(core::any::type_name::<T>()),
name: DebugName::type_name::<T>(),
// PERF: `SparseStorage` may actually be a more
// reasonable choice as `storage_type` for resources.
storage_type: StorageType::Table,
@ -1173,7 +937,7 @@ impl ComponentDescriptor {
fn new_non_send<T: Any>(storage_type: StorageType) -> Self {
Self {
name: Cow::Borrowed(core::any::type_name::<T>()),
name: DebugName::type_name::<T>(),
storage_type,
is_send_and_sync: false,
type_id: Some(TypeId::of::<T>()),
@ -1199,8 +963,8 @@ impl ComponentDescriptor {
/// Returns the name of the current component.
#[inline]
pub fn name(&self) -> &str {
self.name.as_ref()
pub fn name(&self) -> DebugName {
self.name.clone()
}
/// Returns whether this component is mutable.
@ -2052,7 +1816,7 @@ impl Components {
}
/// Gets the metadata associated with the given component, if it is registered.
/// This will return `None` if the id is not regiserted or is queued.
/// This will return `None` if the id is not registered or is queued.
///
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
#[inline]
@ -2089,13 +1853,10 @@ impl Components {
///
/// 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]
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, str>> {
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<DebugName> {
self.components
.get(id.0)
.and_then(|info| {
info.as_ref()
.map(|info| Cow::Borrowed(info.descriptor.name()))
})
.and_then(|info| info.as_ref().map(|info| info.descriptor.name()))
.or_else(|| {
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
// first check components, then resources, then dynamic
@ -2616,12 +2377,12 @@ impl Tick {
///
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
#[inline]
pub(crate) fn check_tick(&mut self, tick: Tick) -> bool {
let age = tick.relative_to(*self);
pub fn check_tick(&mut self, check: CheckChangeTicks) -> bool {
let age = check.present_tick().relative_to(*self);
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
// so long as this check always runs before that can happen.
if age.get() > Self::MAX.get() {
*self = tick.relative_to(Self::MAX);
*self = check.present_tick().relative_to(Self::MAX);
true
} else {
false
@ -2629,6 +2390,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(|check: On<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
/// schedule.0.check_change_ticks(*check);
/// });
/// ```
#[derive(Debug, Clone, Copy, Event)]
pub struct CheckChangeTicks(pub(crate) Tick);
impl CheckChangeTicks {
/// Get the present `Tick` that other ticks get compared to.
pub fn present_tick(self) -> Tick {
self.0
}
}
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
#[derive(Copy, Clone, Debug)]
pub struct TickCells<'a> {
@ -3013,13 +2809,13 @@ pub fn enforce_no_required_components_recursion(
"Recursive required components detected: {}\nhelp: {}",
recursion_check_stack
.iter()
.map(|id| format!("{}", ShortName(&components.get_name(*id).unwrap())))
.map(|id| format!("{}", components.get_name(*id).unwrap().shortname()))
.collect::<Vec<_>>()
.join(""),
if direct_recursion {
format!(
"Remove require({}).",
ShortName(&components.get_name(requiree).unwrap())
components.get_name(requiree).unwrap().shortname()
)
} else {
"If this is intentional, consider merging the components.".into()

View File

@ -1,6 +1,7 @@
use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_ptr::{Ptr, PtrMut};
use bevy_utils::prelude::DebugName;
use bumpalo::Bump;
use core::any::TypeId;
@ -171,7 +172,8 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// - `ComponentId` of component being written does not match expected `ComponentId`.
pub fn write_target_component<C: Component>(&mut self, mut component: C) {
C::map_entities(&mut component, &mut self.mapper);
let short_name = disqualified::ShortName::of::<C>();
let debug_name = DebugName::type_name::<C>();
let short_name = debug_name.shortname();
if self.target_component_written {
panic!("Trying to write component '{short_name}' multiple times")
}

View File

@ -76,7 +76,7 @@ pub use unique_vec::{UniqueEntityEquivalentVec, UniqueEntityVec};
use crate::{
archetype::{ArchetypeId, ArchetypeRow},
change_detection::MaybeLocation,
component::Tick,
component::{CheckChangeTicks, Tick},
storage::{SparseSetIndex, TableId, TableRow},
};
use alloc::vec::Vec;
@ -1216,9 +1216,9 @@ impl Entities {
}
#[inline]
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) {
for meta in &mut self.meta {
meta.spawned_or_despawned.at.check_tick(change_tick);
meta.spawned_or_despawned.at.check_tick(check);
}
}

View File

@ -1,4 +1,6 @@
use core::{any::type_name, fmt};
use core::fmt;
use bevy_utils::prelude::DebugName;
use crate::{
entity::Entity,
@ -31,7 +33,7 @@ where
Err(err) => (error_handler)(
err.into(),
ErrorContext::Command {
name: type_name::<C>().into(),
name: DebugName::type_name::<C>(),
},
),
}
@ -43,7 +45,7 @@ where
Err(err) => world.default_error_handler()(
err.into(),
ErrorContext::Command {
name: type_name::<C>().into(),
name: DebugName::type_name::<C>(),
},
),
}

View File

@ -1,7 +1,7 @@
use core::fmt::Display;
use crate::{component::Tick, error::BevyError, prelude::Resource};
use alloc::borrow::Cow;
use bevy_utils::prelude::DebugName;
use derive_more::derive::{Deref, DerefMut};
/// Context for a [`BevyError`] to aid in debugging.
@ -10,26 +10,26 @@ pub enum ErrorContext {
/// The error occurred in a system.
System {
/// The name of the system that failed.
name: Cow<'static, str>,
name: DebugName,
/// The last tick that the system was run.
last_run: Tick,
},
/// The error occurred in a run condition.
RunCondition {
/// The name of the run condition that failed.
name: Cow<'static, str>,
name: DebugName,
/// The last tick that the run condition was evaluated.
last_run: Tick,
},
/// The error occurred in a command.
Command {
/// The name of the command that failed.
name: Cow<'static, str>,
name: DebugName,
},
/// The error occurred in an observer.
Observer {
/// The name of the observer that failed.
name: Cow<'static, str>,
name: DebugName,
/// The last tick that the observer was run.
last_run: Tick,
},
@ -54,12 +54,12 @@ impl Display for ErrorContext {
impl ErrorContext {
/// The name of the ECS construct that failed.
pub fn name(&self) -> &str {
pub fn name(&self) -> DebugName {
match self {
Self::System { name, .. }
| Self::Command { name, .. }
| Self::Observer { name, .. }
| Self::RunCondition { name, .. } => name,
| Self::RunCondition { name, .. } => name.clone(),
}
}

View File

@ -11,33 +11,81 @@ use core::{
marker::PhantomData,
};
/// Something that "happens" and might be read / observed by app logic.
/// Something that "happens" and can be processed by app logic.
///
/// Events can be stored in an [`Events<E>`] resource
/// You can conveniently access events using the [`EventReader`] and [`EventWriter`] system parameter.
/// Events can be triggered on a [`World`] using a method like [`trigger`](World::trigger),
/// causing any global [`Observer`] watching that event to run. This allows for push-based
/// event handling where observers are immediately notified of events as they happen.
///
/// Events can also be "triggered" on a [`World`], which will then cause any [`Observer`] of that trigger to run.
/// Additional event handling behavior can be enabled by implementing the [`EntityEvent`]
/// and [`BufferedEvent`] traits:
///
/// - [`EntityEvent`]s support targeting specific entities, triggering any observers watching those targets.
/// They are useful for entity-specific event handlers and can even be propagated from one entity to another.
/// - [`BufferedEvent`]s support a pull-based event handling system where events are written using an [`EventWriter`]
/// and read later using an [`EventReader`]. This is an alternative to observers that allows efficient batch processing
/// of events at fixed points in a schedule.
///
/// Events must be thread-safe.
///
/// ## Derive
/// This trait can be derived.
/// Adding `auto_propagate` sets [`Self::AUTO_PROPAGATE`] to true.
/// Adding `traversal = "X"` sets [`Self::Traversal`] to be of type "X".
/// # Usage
///
/// The [`Event`] trait can be derived:
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// #[event(auto_propagate)]
/// struct MyEvent;
/// struct Speak {
/// message: String,
/// }
/// ```
///
/// An [`Observer`] can then be added to listen for this event type:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # struct Speak {
/// # message: String,
/// # }
/// #
/// # let mut world = World::new();
/// #
/// world.add_observer(|trigger: On<Speak>| {
/// println!("{}", trigger.message);
/// });
/// ```
///
/// The event can be triggered on the [`World`] using the [`trigger`](World::trigger) method:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event)]
/// # struct Speak {
/// # message: String,
/// # }
/// #
/// # let mut world = World::new();
/// #
/// # world.add_observer(|trigger: On<Speak>| {
/// # println!("{}", trigger.message);
/// # });
/// #
/// # world.flush();
/// #
/// world.trigger(Speak {
/// message: "Hello!".to_string(),
/// });
/// ```
///
/// For events that additionally need entity targeting or buffering, consider also deriving
/// [`EntityEvent`] or [`BufferedEvent`], respectively.
///
/// [`World`]: crate::world::World
/// [`ComponentId`]: crate::component::ComponentId
/// [`Observer`]: crate::observer::Observer
/// [`Events<E>`]: super::Events
/// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter
#[diagnostic::on_unimplemented(
@ -46,18 +94,6 @@ use core::{
note = "consider annotating `{Self}` with `#[derive(Event)]`"
)]
pub trait Event: Send + Sync + 'static {
/// The component that describes which Entity to propagate this event to next, when [propagation] is enabled.
///
/// [propagation]: crate::observer::Trigger::propagate
type Traversal: Traversal<Self>;
/// When true, this event will always attempt to propagate when [triggered], without requiring a call
/// to [`Trigger::propagate`].
///
/// [triggered]: crate::system::Commands::trigger_targets
/// [`Trigger::propagate`]: crate::observer::Trigger::propagate
const AUTO_PROPAGATE: bool = false;
/// Generates the [`ComponentId`] for this event type.
///
/// If this type has already been registered,
@ -68,7 +104,7 @@ pub trait Event: Send + Sync + 'static {
///
/// # Warning
///
/// This method should not be overridden by implementors,
/// This method should not be overridden by implementers,
/// and should always correspond to the implementation of [`component_id`](Event::component_id).
fn register_component_id(world: &mut World) -> ComponentId {
world.register_component::<EventWrapperComponent<Self>>()
@ -82,13 +118,216 @@ pub trait Event: Send + Sync + 'static {
///
/// # Warning
///
/// This method should not be overridden by implementors,
/// This method should not be overridden by implementers,
/// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id).
fn component_id(world: &World) -> Option<ComponentId> {
world.component_id::<EventWrapperComponent<Self>>()
}
}
/// An [`Event`] that can be targeted at specific entities.
///
/// Entity events can be triggered on a [`World`] with specific entity targets using a method
/// like [`trigger_targets`](World::trigger_targets), causing any [`Observer`] watching the event
/// for those entities to run.
///
/// Unlike basic [`Event`]s, entity events can optionally be propagated from one entity target to another
/// based on the [`EntityEvent::Traversal`] type associated with the event. This enables use cases
/// such as bubbling events to parent entities for UI purposes.
///
/// Entity events must be thread-safe.
///
/// # Usage
///
/// The [`EntityEvent`] trait can be derived. The `event` attribute can be used to further configure
/// the propagation behavior: adding `auto_propagate` sets [`EntityEvent::AUTO_PROPAGATE`] to `true`,
/// while adding `traversal = X` sets [`EntityEvent::Traversal`] to be of type `X`.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// // When the `Damage` event is triggered on an entity, bubble the event up to ancestors.
/// #[derive(Event, EntityEvent)]
/// #[entity_event(traversal = &'static ChildOf, auto_propagate)]
/// struct Damage {
/// amount: f32,
/// }
/// ```
///
/// An [`Observer`] can then be added to listen for this event type for the desired entity:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event, EntityEvent)]
/// # #[entity_event(traversal = &'static ChildOf, auto_propagate)]
/// # struct Damage {
/// # amount: f32,
/// # }
/// #
/// # #[derive(Component)]
/// # struct Health(f32);
/// #
/// # #[derive(Component)]
/// # struct Enemy;
/// #
/// # #[derive(Component)]
/// # struct ArmorPiece;
/// #
/// # let mut world = World::new();
/// #
/// // Spawn an enemy entity.
/// let enemy = world.spawn((Enemy, Health(100.0))).id();
///
/// // Spawn some armor as a child of the enemy entity.
/// // When the armor takes damage, it will bubble the event up to the enemy,
/// // which can then handle the event with its own observer.
/// let armor_piece = world
/// .spawn((ArmorPiece, Health(25.0), ChildOf(enemy)))
/// .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| {
/// // Note: `On::target` only exists because this is an `EntityEvent`.
/// let mut health = query.get_mut(trigger.target()).unwrap();
/// health.0 -= trigger.amount;
/// })
/// .id();
/// ```
///
/// The event can be triggered on the [`World`] using the [`trigger_targets`](World::trigger_targets) method,
/// providing the desired entity target(s):
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event, EntityEvent)]
/// # #[entity_event(traversal = &'static ChildOf, auto_propagate)]
/// # struct Damage {
/// # amount: f32,
/// # }
/// #
/// # #[derive(Component)]
/// # struct Health(f32);
/// #
/// # #[derive(Component)]
/// # struct Enemy;
/// #
/// # #[derive(Component)]
/// # struct ArmorPiece;
/// #
/// # let mut world = World::new();
/// #
/// # let enemy = world.spawn((Enemy, Health(100.0))).id();
/// # let armor_piece = world
/// # .spawn((ArmorPiece, Health(25.0), ChildOf(enemy)))
/// # .observe(|trigger: On<Damage>, mut query: Query<&mut Health>| {
/// # // Note: `On::target` only exists because this is an `EntityEvent`.
/// # let mut health = query.get_mut(trigger.target()).unwrap();
/// # health.0 -= trigger.amount;
/// # })
/// # .id();
/// #
/// # world.flush();
/// #
/// world.trigger_targets(Damage { amount: 10.0 }, armor_piece);
/// ```
///
/// [`World`]: crate::world::World
/// [`TriggerTargets`]: crate::observer::TriggerTargets
/// [`Observer`]: crate::observer::Observer
/// [`Events<E>`]: super::Events
/// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `EntityEvent`",
label = "invalid `EntityEvent`",
note = "consider annotating `{Self}` with `#[derive(Event, EntityEvent)]`"
)]
pub trait EntityEvent: Event {
/// The component that describes which [`Entity`] to propagate this event to next, when [propagation] is enabled.
///
/// [`Entity`]: crate::entity::Entity
/// [propagation]: crate::observer::On::propagate
type Traversal: Traversal<Self>;
/// When true, this event will always attempt to propagate when [triggered], without requiring a call
/// to [`On::propagate`].
///
/// [triggered]: crate::system::Commands::trigger_targets
/// [`On::propagate`]: crate::observer::On::propagate
const AUTO_PROPAGATE: bool = false;
}
/// A buffered [`Event`] for pull-based event handling.
///
/// Buffered events can be written with [`EventWriter`] and read using the [`EventReader`] system parameter.
/// These events are stored in the [`Events<E>`] resource, and require periodically polling the world for new events,
/// typically in a system that runs as part of a schedule.
///
/// While the polling imposes a small overhead, buffered events are useful for efficiently batch processing
/// a large number of events at once. This can make them more efficient than [`Event`]s used by [`Observer`]s
/// for events that happen at a high frequency or in large quantities.
///
/// Unlike [`Event`]s triggered for observers, buffered events are evaluated at fixed points in the schedule
/// rather than immediately when they are sent. This allows for more predictable scheduling and deferring
/// event processing to a later point in time.
///
/// Buffered events do *not* trigger observers automatically when they are written via an [`EventWriter`].
/// However, they can still also be triggered on a [`World`] in case you want both buffered and immediate
/// event handling for the same event.
///
/// Buffered events must be thread-safe.
///
/// # Usage
///
/// The [`BufferedEvent`] trait can be derived:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event, BufferedEvent)]
/// struct Message(String);
/// ```
///
/// The event can then be written to the event buffer using an [`EventWriter`]:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event, BufferedEvent)]
/// # struct Message(String);
/// #
/// fn write_hello(mut writer: EventWriter<Message>) {
/// writer.write(Message("Hello!".to_string()));
/// }
/// ```
///
/// Buffered events can be efficiently read using an [`EventReader`]:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// # #[derive(Event, BufferedEvent)]
/// # struct Message(String);
/// #
/// fn read_messages(mut reader: EventReader<Message>) {
/// // Process all buffered events of type `Message`.
/// for Message(message) in reader.read() {
/// println!("{message}");
/// }
/// }
/// ```
///
/// [`World`]: crate::world::World
/// [`Observer`]: crate::observer::Observer
/// [`Events<E>`]: super::Events
/// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `BufferedEvent`",
label = "invalid `BufferedEvent`",
note = "consider annotating `{Self}` with `#[derive(Event, BufferedEvent)]`"
)]
pub trait BufferedEvent: Event {}
/// An internal type that implements [`Component`] for a given [`Event`] type.
///
/// This exists so we can easily get access to a unique [`ComponentId`] for each [`Event`] type,
@ -115,7 +354,7 @@ struct EventWrapperComponent<E: Event + ?Sized>(PhantomData<E>);
derive(Reflect),
reflect(Clone, Debug, PartialEq, Hash)
)]
pub struct EventId<E: Event> {
pub struct EventId<E: BufferedEvent> {
/// Uniquely identifies the event associated with this ID.
// This value corresponds to the order in which each event was added to the world.
pub id: usize,
@ -125,21 +364,21 @@ pub struct EventId<E: Event> {
pub(super) _marker: PhantomData<E>,
}
impl<E: Event> Copy for EventId<E> {}
impl<E: BufferedEvent> Copy for EventId<E> {}
impl<E: Event> Clone for EventId<E> {
impl<E: BufferedEvent> Clone for EventId<E> {
fn clone(&self) -> Self {
*self
}
}
impl<E: Event> fmt::Display for EventId<E> {
impl<E: BufferedEvent> fmt::Display for EventId<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<E: Event> fmt::Debug for EventId<E> {
impl<E: BufferedEvent> fmt::Debug for EventId<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
@ -150,27 +389,27 @@ impl<E: Event> fmt::Debug for EventId<E> {
}
}
impl<E: Event> PartialEq for EventId<E> {
impl<E: BufferedEvent> PartialEq for EventId<E> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<E: Event> Eq for EventId<E> {}
impl<E: BufferedEvent> Eq for EventId<E> {}
impl<E: Event> PartialOrd for EventId<E> {
impl<E: BufferedEvent> PartialOrd for EventId<E> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<E: Event> Ord for EventId<E> {
impl<E: BufferedEvent> Ord for EventId<E> {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl<E: Event> Hash for EventId<E> {
impl<E: BufferedEvent> Hash for EventId<E> {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.id, state);
}
@ -178,7 +417,7 @@ impl<E: Event> Hash for EventId<E> {
#[derive(Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub(crate) struct EventInstance<E: Event> {
pub(crate) struct EventInstance<E: BufferedEvent> {
pub event_id: EventId<E>,
pub event: E,
}

View File

@ -1,7 +1,7 @@
use alloc::vec::Vec;
use bevy_ecs::{
change_detection::MaybeLocation,
event::{Event, EventCursor, EventId, EventInstance},
event::{BufferedEvent, EventCursor, EventId, EventInstance},
resource::Resource,
};
use core::{
@ -38,10 +38,11 @@ use {
/// dropped silently.
///
/// # Example
/// ```
/// use bevy_ecs::event::{Event, Events};
///
/// #[derive(Event)]
/// ```
/// use bevy_ecs::event::{BufferedEvent, Event, Events};
///
/// #[derive(Event, BufferedEvent)]
/// struct MyEvent {
/// value: usize
/// }
@ -91,7 +92,7 @@ use {
/// [`event_update_system`]: super::event_update_system
#[derive(Debug, Resource)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Default))]
pub struct Events<E: Event> {
pub struct Events<E: BufferedEvent> {
/// Holds the oldest still active events.
/// Note that `a.start_event_count + a.len()` should always be equal to `events_b.start_event_count`.
pub(crate) events_a: EventSequence<E>,
@ -101,7 +102,7 @@ pub struct Events<E: Event> {
}
// Derived Default impl would incorrectly require E: Default
impl<E: Event> Default for Events<E> {
impl<E: BufferedEvent> Default for Events<E> {
fn default() -> Self {
Self {
events_a: Default::default(),
@ -111,7 +112,7 @@ impl<E: Event> Default for Events<E> {
}
}
impl<E: Event> Events<E> {
impl<E: BufferedEvent> Events<E> {
/// Returns the index of the oldest event stored in the event buffer.
pub fn oldest_event_count(&self) -> usize {
self.events_a.start_event_count
@ -286,7 +287,7 @@ impl<E: Event> Events<E> {
}
}
impl<E: Event> Extend<E> for Events<E> {
impl<E: BufferedEvent> Extend<E> for Events<E> {
#[track_caller]
fn extend<I>(&mut self, iter: I)
where
@ -321,13 +322,13 @@ impl<E: Event> Extend<E> for Events<E> {
#[derive(Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default))]
pub(crate) struct EventSequence<E: Event> {
pub(crate) struct EventSequence<E: BufferedEvent> {
pub(crate) events: Vec<EventInstance<E>>,
pub(crate) start_event_count: usize,
}
// Derived Default impl would incorrectly require E: Default
impl<E: Event> Default for EventSequence<E> {
impl<E: BufferedEvent> Default for EventSequence<E> {
fn default() -> Self {
Self {
events: Default::default(),
@ -336,7 +337,7 @@ impl<E: Event> Default for EventSequence<E> {
}
}
impl<E: Event> Deref for EventSequence<E> {
impl<E: BufferedEvent> Deref for EventSequence<E> {
type Target = Vec<EventInstance<E>>;
fn deref(&self) -> &Self::Target {
@ -344,7 +345,7 @@ impl<E: Event> Deref for EventSequence<E> {
}
}
impl<E: Event> DerefMut for EventSequence<E> {
impl<E: BufferedEvent> DerefMut for EventSequence<E> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.events
}
@ -357,7 +358,7 @@ pub struct SendBatchIds<E> {
_marker: PhantomData<E>,
}
impl<E: Event> Iterator for SendBatchIds<E> {
impl<E: BufferedEvent> Iterator for SendBatchIds<E> {
type Item = EventId<E>;
fn next(&mut self) -> Option<Self::Item> {
@ -377,7 +378,7 @@ impl<E: Event> Iterator for SendBatchIds<E> {
}
}
impl<E: Event> ExactSizeIterator for SendBatchIds<E> {
impl<E: BufferedEvent> ExactSizeIterator for SendBatchIds<E> {
fn len(&self) -> usize {
self.event_count.saturating_sub(self.last_count)
}
@ -385,12 +386,11 @@ impl<E: Event> ExactSizeIterator for SendBatchIds<E> {
#[cfg(test)]
mod tests {
use crate::event::Events;
use bevy_ecs_macros::Event;
use crate::event::{BufferedEvent, Event, Events};
#[test]
fn iter_current_update_events_iterates_over_current_events() {
#[derive(Event, Clone)]
#[derive(Event, BufferedEvent, Clone)]
struct TestEvent;
let mut test_events = Events::<TestEvent>::default();

View File

@ -1,5 +1,6 @@
use bevy_ecs::event::{
Event, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId, Events,
BufferedEvent, EventIterator, EventIteratorWithId, EventMutIterator, EventMutIteratorWithId,
Events,
};
#[cfg(feature = "multi_threaded")]
use bevy_ecs::event::{EventMutParIter, EventParIter};
@ -19,9 +20,9 @@ use core::marker::PhantomData;
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_ecs::event::{Event, Events, EventCursor};
/// use bevy_ecs::event::{BufferedEvent, Events, EventCursor};
///
/// #[derive(Event, Clone, Debug)]
/// #[derive(Event, BufferedEvent, Clone, Debug)]
/// struct MyEvent;
///
/// /// A system that both sends and receives events using a [`Local`] [`EventCursor`].
@ -50,12 +51,12 @@ use core::marker::PhantomData;
/// [`EventReader`]: super::EventReader
/// [`EventMutator`]: super::EventMutator
#[derive(Debug)]
pub struct EventCursor<E: Event> {
pub struct EventCursor<E: BufferedEvent> {
pub(super) last_event_count: usize,
pub(super) _marker: PhantomData<E>,
}
impl<E: Event> Default for EventCursor<E> {
impl<E: BufferedEvent> Default for EventCursor<E> {
fn default() -> Self {
EventCursor {
last_event_count: 0,
@ -64,7 +65,7 @@ impl<E: Event> Default for EventCursor<E> {
}
}
impl<E: Event> Clone for EventCursor<E> {
impl<E: BufferedEvent> Clone for EventCursor<E> {
fn clone(&self) -> Self {
EventCursor {
last_event_count: self.last_event_count,
@ -73,7 +74,7 @@ impl<E: Event> Clone for EventCursor<E> {
}
}
impl<E: Event> EventCursor<E> {
impl<E: BufferedEvent> EventCursor<E> {
/// See [`EventReader::read`](super::EventReader::read)
pub fn read<'a>(&'a mut self, events: &'a Events<E>) -> EventIterator<'a, E> {
self.read_with_id(events).without_id()

View File

@ -1,15 +1,15 @@
#[cfg(feature = "multi_threaded")]
use bevy_ecs::batching::BatchingStrategy;
use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events};
use bevy_ecs::event::{BufferedEvent, EventCursor, EventId, EventInstance, Events};
use core::{iter::Chain, slice::Iter};
/// An iterator that yields any unread events from an [`EventReader`](super::EventReader) or [`EventCursor`].
#[derive(Debug)]
pub struct EventIterator<'a, E: Event> {
pub struct EventIterator<'a, E: BufferedEvent> {
iter: EventIteratorWithId<'a, E>,
}
impl<'a, E: Event> Iterator for EventIterator<'a, E> {
impl<'a, E: BufferedEvent> Iterator for EventIterator<'a, E> {
type Item = &'a E;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(event, _)| event)
@ -35,7 +35,7 @@ impl<'a, E: Event> Iterator for EventIterator<'a, E> {
}
}
impl<'a, E: Event> ExactSizeIterator for EventIterator<'a, E> {
impl<'a, E: BufferedEvent> ExactSizeIterator for EventIterator<'a, E> {
fn len(&self) -> usize {
self.iter.len()
}
@ -43,13 +43,13 @@ impl<'a, E: Event> ExactSizeIterator for EventIterator<'a, E> {
/// An iterator that yields any unread events (and their IDs) from an [`EventReader`](super::EventReader) or [`EventCursor`].
#[derive(Debug)]
pub struct EventIteratorWithId<'a, E: Event> {
pub struct EventIteratorWithId<'a, E: BufferedEvent> {
reader: &'a mut EventCursor<E>,
chain: Chain<Iter<'a, EventInstance<E>>, Iter<'a, EventInstance<E>>>,
unread: usize,
}
impl<'a, E: Event> EventIteratorWithId<'a, E> {
impl<'a, E: BufferedEvent> EventIteratorWithId<'a, E> {
/// Creates a new iterator that yields any `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut EventCursor<E>, events: &'a Events<E>) -> Self {
let a_index = reader
@ -81,7 +81,7 @@ impl<'a, E: Event> EventIteratorWithId<'a, E> {
}
}
impl<'a, E: Event> Iterator for EventIteratorWithId<'a, E> {
impl<'a, E: BufferedEvent> Iterator for EventIteratorWithId<'a, E> {
type Item = (&'a E, EventId<E>);
fn next(&mut self) -> Option<Self::Item> {
match self
@ -131,16 +131,16 @@ impl<'a, E: Event> Iterator for EventIteratorWithId<'a, E> {
}
}
impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
impl<'a, E: BufferedEvent> ExactSizeIterator for EventIteratorWithId<'a, E> {
fn len(&self) -> usize {
self.unread
}
}
/// A parallel iterator over `Event`s.
/// A parallel iterator over `BufferedEvent`s.
#[cfg(feature = "multi_threaded")]
#[derive(Debug)]
pub struct EventParIter<'a, E: Event> {
pub struct EventParIter<'a, E: BufferedEvent> {
reader: &'a mut EventCursor<E>,
slices: [&'a [EventInstance<E>]; 2],
batching_strategy: BatchingStrategy,
@ -149,7 +149,7 @@ pub struct EventParIter<'a, E: Event> {
}
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> EventParIter<'a, E> {
impl<'a, E: BufferedEvent> EventParIter<'a, E> {
/// Creates a new parallel iterator over `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut EventCursor<E>, events: &'a Events<E>) -> Self {
let a_index = reader
@ -248,7 +248,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
}
}
/// Returns the number of [`Event`]s to be iterated.
/// Returns the number of [`BufferedEvent`]s to be iterated.
pub fn len(&self) -> usize {
self.slices.iter().map(|s| s.len()).sum()
}
@ -260,7 +260,7 @@ impl<'a, E: Event> EventParIter<'a, E> {
}
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> IntoIterator for EventParIter<'a, E> {
impl<'a, E: BufferedEvent> IntoIterator for EventParIter<'a, E> {
type IntoIter = EventIteratorWithId<'a, E>;
type Item = <Self::IntoIter as Iterator>::Item;

View File

@ -11,8 +11,8 @@ mod update;
mod writer;
pub(crate) use base::EventInstance;
pub use base::{Event, EventId};
pub use bevy_ecs_macros::Event;
pub use base::{BufferedEvent, EntityEvent, Event, EventId};
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event};
pub use collections::{Events, SendBatchIds};
pub use event_cursor::EventCursor;
#[cfg(feature = "multi_threaded")]
@ -38,17 +38,20 @@ pub use writer::EventWriter;
mod tests {
use alloc::{vec, vec::Vec};
use bevy_ecs::{event::*, system::assert_is_read_only_system};
use bevy_ecs_macros::Event;
use bevy_ecs_macros::BufferedEvent;
#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)]
#[derive(Event, BufferedEvent, Copy, Clone, PartialEq, Eq, Debug)]
struct TestEvent {
i: usize,
}
#[derive(Event, Clone, PartialEq, Debug, Default)]
#[derive(Event, BufferedEvent, Clone, PartialEq, Debug, Default)]
struct EmptyTestEvent;
fn get_events<E: Event + Clone>(events: &Events<E>, cursor: &mut EventCursor<E>) -> Vec<E> {
fn get_events<E: BufferedEvent + Clone>(
events: &Events<E>,
cursor: &mut EventCursor<E>,
) -> Vec<E> {
cursor.read(events).cloned().collect::<Vec<E>>()
}

View File

@ -1,17 +1,17 @@
#[cfg(feature = "multi_threaded")]
use bevy_ecs::batching::BatchingStrategy;
use bevy_ecs::event::{Event, EventCursor, EventId, EventInstance, Events};
use bevy_ecs::event::{BufferedEvent, EventCursor, EventId, EventInstance, Events};
use core::{iter::Chain, slice::IterMut};
/// An iterator that yields any unread events from an [`EventMutator`] or [`EventCursor`].
///
/// [`EventMutator`]: super::EventMutator
#[derive(Debug)]
pub struct EventMutIterator<'a, E: Event> {
pub struct EventMutIterator<'a, E: BufferedEvent> {
iter: EventMutIteratorWithId<'a, E>,
}
impl<'a, E: Event> Iterator for EventMutIterator<'a, E> {
impl<'a, E: BufferedEvent> Iterator for EventMutIterator<'a, E> {
type Item = &'a mut E;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(event, _)| event)
@ -37,7 +37,7 @@ impl<'a, E: Event> Iterator for EventMutIterator<'a, E> {
}
}
impl<'a, E: Event> ExactSizeIterator for EventMutIterator<'a, E> {
impl<'a, E: BufferedEvent> ExactSizeIterator for EventMutIterator<'a, E> {
fn len(&self) -> usize {
self.iter.len()
}
@ -47,13 +47,13 @@ impl<'a, E: Event> ExactSizeIterator for EventMutIterator<'a, E> {
///
/// [`EventMutator`]: super::EventMutator
#[derive(Debug)]
pub struct EventMutIteratorWithId<'a, E: Event> {
pub struct EventMutIteratorWithId<'a, E: BufferedEvent> {
mutator: &'a mut EventCursor<E>,
chain: Chain<IterMut<'a, EventInstance<E>>, IterMut<'a, EventInstance<E>>>,
unread: usize,
}
impl<'a, E: Event> EventMutIteratorWithId<'a, E> {
impl<'a, E: BufferedEvent> EventMutIteratorWithId<'a, E> {
/// Creates a new iterator that yields any `events` that have not yet been seen by `mutator`.
pub fn new(mutator: &'a mut EventCursor<E>, events: &'a mut Events<E>) -> Self {
let a_index = mutator
@ -84,7 +84,7 @@ impl<'a, E: Event> EventMutIteratorWithId<'a, E> {
}
}
impl<'a, E: Event> Iterator for EventMutIteratorWithId<'a, E> {
impl<'a, E: BufferedEvent> Iterator for EventMutIteratorWithId<'a, E> {
type Item = (&'a mut E, EventId<E>);
fn next(&mut self) -> Option<Self::Item> {
match self
@ -134,16 +134,16 @@ impl<'a, E: Event> Iterator for EventMutIteratorWithId<'a, E> {
}
}
impl<'a, E: Event> ExactSizeIterator for EventMutIteratorWithId<'a, E> {
impl<'a, E: BufferedEvent> ExactSizeIterator for EventMutIteratorWithId<'a, E> {
fn len(&self) -> usize {
self.unread
}
}
/// A parallel iterator over `Event`s.
/// A parallel iterator over `BufferedEvent`s.
#[derive(Debug)]
#[cfg(feature = "multi_threaded")]
pub struct EventMutParIter<'a, E: Event> {
pub struct EventMutParIter<'a, E: BufferedEvent> {
mutator: &'a mut EventCursor<E>,
slices: [&'a mut [EventInstance<E>]; 2],
batching_strategy: BatchingStrategy,
@ -152,7 +152,7 @@ pub struct EventMutParIter<'a, E: Event> {
}
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> EventMutParIter<'a, E> {
impl<'a, E: BufferedEvent> EventMutParIter<'a, E> {
/// Creates a new parallel iterator over `events` that have not yet been seen by `mutator`.
pub fn new(mutator: &'a mut EventCursor<E>, events: &'a mut Events<E>) -> Self {
let a_index = mutator
@ -251,7 +251,7 @@ impl<'a, E: Event> EventMutParIter<'a, E> {
}
}
/// Returns the number of [`Event`]s to be iterated.
/// Returns the number of [`BufferedEvent`]s to be iterated.
pub fn len(&self) -> usize {
self.slices.iter().map(|s| s.len()).sum()
}
@ -263,7 +263,7 @@ impl<'a, E: Event> EventMutParIter<'a, E> {
}
#[cfg(feature = "multi_threaded")]
impl<'a, E: Event> IntoIterator for EventMutParIter<'a, E> {
impl<'a, E: BufferedEvent> IntoIterator for EventMutParIter<'a, E> {
type IntoIter = EventMutIteratorWithId<'a, E>;
type Item = <Self::IntoIter as Iterator>::Item;

View File

@ -1,7 +1,7 @@
#[cfg(feature = "multi_threaded")]
use bevy_ecs::event::EventMutParIter;
use bevy_ecs::{
event::{Event, EventCursor, EventMutIterator, EventMutIteratorWithId, Events},
event::{BufferedEvent, EventCursor, EventMutIterator, EventMutIteratorWithId, Events},
system::{Local, ResMut, SystemParam},
};
@ -15,7 +15,7 @@ use bevy_ecs::{
/// ```
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Event, Debug)]
/// #[derive(Event, BufferedEvent, Debug)]
/// pub struct MyEvent(pub u32); // Custom event type.
/// fn my_system(mut reader: EventMutator<MyEvent>) {
/// for event in reader.read() {
@ -42,13 +42,13 @@ use bevy_ecs::{
/// [`EventReader`]: super::EventReader
/// [`EventWriter`]: super::EventWriter
#[derive(SystemParam, Debug)]
pub struct EventMutator<'w, 's, E: Event> {
pub struct EventMutator<'w, 's, E: BufferedEvent> {
pub(super) reader: Local<'s, EventCursor<E>>,
#[system_param(validation_message = "Event not initialized")]
#[system_param(validation_message = "BufferedEvent not initialized")]
events: ResMut<'w, Events<E>>,
}
impl<'w, 's, E: Event> EventMutator<'w, 's, E> {
impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> {
/// Iterates over the events this [`EventMutator`] has not seen yet. This updates the
/// [`EventMutator`]'s event counter, which means subsequent event reads will not include events
/// that happened before now.
@ -69,7 +69,7 @@ impl<'w, 's, E: Event> EventMutator<'w, 's, E> {
/// # use bevy_ecs::prelude::*;
/// # use std::sync::atomic::{AtomicUsize, Ordering};
///
/// #[derive(Event)]
/// #[derive(Event, BufferedEvent)]
/// struct MyEvent {
/// value: usize,
/// }
@ -116,7 +116,7 @@ impl<'w, 's, E: Event> EventMutator<'w, 's, E> {
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// #[derive(Event, BufferedEvent)]
/// struct CollisionEvent;
///
/// fn play_collision_sound(mut events: EventMutator<CollisionEvent>) {

View File

@ -1,11 +1,11 @@
#[cfg(feature = "multi_threaded")]
use bevy_ecs::event::EventParIter;
use bevy_ecs::{
event::{Event, EventCursor, EventIterator, EventIteratorWithId, Events},
event::{BufferedEvent, EventCursor, EventIterator, EventIteratorWithId, Events},
system::{Local, Res, SystemParam},
};
/// Reads events of type `T` in order and tracks which events have already been read.
/// Reads [`BufferedEvent`]s of type `T` in order and tracks which events have already been read.
///
/// # Concurrency
///
@ -14,13 +14,13 @@ use bevy_ecs::{
///
/// [`EventWriter<T>`]: super::EventWriter
#[derive(SystemParam, Debug)]
pub struct EventReader<'w, 's, E: Event> {
pub struct EventReader<'w, 's, E: BufferedEvent> {
pub(super) reader: Local<'s, EventCursor<E>>,
#[system_param(validation_message = "Event not initialized")]
#[system_param(validation_message = "BufferedEvent not initialized")]
events: Res<'w, Events<E>>,
}
impl<'w, 's, E: Event> EventReader<'w, 's, E> {
impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> {
/// Iterates over the events this [`EventReader`] has not seen yet. This updates the
/// [`EventReader`]'s event counter, which means subsequent event reads will not include events
/// that happened before now.
@ -41,7 +41,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
/// # use bevy_ecs::prelude::*;
/// # use std::sync::atomic::{AtomicUsize, Ordering};
///
/// #[derive(Event)]
/// #[derive(Event, BufferedEvent)]
/// struct MyEvent {
/// value: usize,
/// }
@ -88,7 +88,7 @@ impl<'w, 's, E: Event> EventReader<'w, 's, E> {
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// #[derive(Event, BufferedEvent)]
/// struct CollisionEvent;
///
/// fn play_collision_sound(mut events: EventReader<CollisionEvent>) {

View File

@ -2,7 +2,7 @@ use alloc::vec::Vec;
use bevy_ecs::{
change_detection::{DetectChangesMut, MutUntyped},
component::{ComponentId, Tick},
event::{Event, Events},
event::{BufferedEvent, Events},
resource::Resource,
world::World,
};
@ -45,7 +45,7 @@ impl EventRegistry {
///
/// If no instance of the [`EventRegistry`] exists in the world, this will add one - otherwise it will use
/// the existing instance.
pub fn register_event<T: Event>(world: &mut World) {
pub fn register_event<T: BufferedEvent>(world: &mut World) {
// By initializing the resource here, we can be sure that it is present,
// and receive the correct, up-to-date `ComponentId` even if it was previously removed.
let component_id = world.init_resource::<Events<T>>();
@ -82,7 +82,7 @@ impl EventRegistry {
}
/// Removes an event from the world and its associated [`EventRegistry`].
pub fn deregister_events<T: Event>(world: &mut World) {
pub fn deregister_events<T: BufferedEvent>(world: &mut World) {
let component_id = world.init_resource::<Events<T>>();
let mut registry = world.get_resource_or_init::<Self>();
registry

View File

@ -1,9 +1,9 @@
use bevy_ecs::{
event::{Event, EventId, Events, SendBatchIds},
event::{BufferedEvent, EventId, Events, SendBatchIds},
system::{ResMut, SystemParam},
};
/// Sends events of type `T`.
/// Sends [`BufferedEvent`]s of type `T`.
///
/// # Usage
///
@ -11,7 +11,7 @@ use bevy_ecs::{
/// ```
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Event)]
/// #[derive(Event, BufferedEvent)]
/// pub struct MyEvent; // Custom event type.
/// fn my_system(mut writer: EventWriter<MyEvent>) {
/// writer.write(MyEvent);
@ -21,8 +21,8 @@ use bevy_ecs::{
/// ```
/// # Observers
///
/// "Buffered" Events, such as those sent directly in [`Events`] or written using [`EventWriter`], do _not_ automatically
/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will
/// "Buffered" events, such as those sent directly in [`Events`] or written using [`EventWriter`], do _not_ automatically
/// trigger any [`Observer`]s watching for that event, as each [`BufferedEvent`] has different requirements regarding _if_ it will
/// be triggered, and if so, _when_ it will be triggered in the schedule.
///
/// # Concurrency
@ -38,7 +38,7 @@ use bevy_ecs::{
///
/// ```
/// # use bevy_ecs::{prelude::*, event::Events};
/// # #[derive(Event)]
/// # #[derive(Event, BufferedEvent)]
/// # pub struct MyEvent;
/// fn send_untyped(mut commands: Commands) {
/// // Send an event of a specific type without having to declare that
@ -59,12 +59,12 @@ use bevy_ecs::{
///
/// [`Observer`]: crate::observer::Observer
#[derive(SystemParam)]
pub struct EventWriter<'w, E: Event> {
#[system_param(validation_message = "Event not initialized")]
pub struct EventWriter<'w, E: BufferedEvent> {
#[system_param(validation_message = "BufferedEvent not initialized")]
events: ResMut<'w, Events<E>>,
}
impl<'w, E: Event> EventWriter<'w, E> {
impl<'w, E: BufferedEvent> EventWriter<'w, E> {
/// Writes an `event`, which can later be read by [`EventReader`](super::EventReader)s.
/// This method returns the [ID](`EventId`) of the written `event`.
///

View File

@ -10,8 +10,9 @@
use crate::reflect::{ReflectComponent, ReflectFromWorld};
use crate::{
bundle::Bundle,
component::{Component, HookContext},
component::Component,
entity::Entity,
lifecycle::HookContext,
relationship::{RelatedSpawner, RelatedSpawnerCommands},
system::EntityCommands,
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
@ -21,9 +22,9 @@ use alloc::{format, string::String, vec::Vec};
use bevy_reflect::std_traits::ReflectDefault;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::prelude::DebugName;
use core::ops::Deref;
use core::slice;
use disqualified::ShortName;
use log::warn;
/// Stores the parent entity of this child entity with this component.
@ -293,6 +294,12 @@ impl<'w> EntityWorldMut<'w> {
self.insert_related::<ChildOf>(index, children)
}
/// Insert child at specific index.
/// See also [`insert_related`](Self::insert_related).
pub fn insert_child(&mut self, index: usize, child: Entity) -> &mut Self {
self.insert_related::<ChildOf>(index, &[child])
}
/// Adds the given child to this entity
/// See also [`add_related`](Self::add_related).
pub fn add_child(&mut self, child: Entity) -> &mut Self {
@ -304,6 +311,11 @@ impl<'w> EntityWorldMut<'w> {
self.remove_related::<ChildOf>(children)
}
/// Removes the relationship between this entity and the given entity.
pub fn remove_child(&mut self, child: Entity) -> &mut Self {
self.remove_related::<ChildOf>(&[child])
}
/// Replaces all the related children with a new set of children.
pub fn replace_children(&mut self, children: &[Entity]) -> &mut Self {
self.replace_related::<ChildOf>(children)
@ -373,6 +385,12 @@ impl<'a> EntityCommands<'a> {
self.insert_related::<ChildOf>(index, children)
}
/// Insert children at specific index.
/// See also [`insert_related`](Self::insert_related).
pub fn insert_child(&mut self, index: usize, child: Entity) -> &mut Self {
self.insert_related::<ChildOf>(index, &[child])
}
/// Adds the given child to this entity
pub fn add_child(&mut self, child: Entity) -> &mut Self {
self.add_related::<ChildOf>(&[child])
@ -383,6 +401,11 @@ impl<'a> EntityCommands<'a> {
self.remove_related::<ChildOf>(children)
}
/// Removes the relationship between this entity and the given entity.
pub fn remove_child(&mut self, child: Entity) -> &mut Self {
self.remove_related::<ChildOf>(&[child])
}
/// Replaces the children on this entity with a new list of children.
pub fn replace_children(&mut self, children: &[Entity]) -> &mut Self {
self.replace_related::<ChildOf>(children)
@ -438,11 +461,12 @@ pub fn validate_parent_has_component<C: Component>(
{
// TODO: print name here once Name lives in bevy_ecs
let name: Option<String> = None;
let debug_name = DebugName::type_name::<C>();
warn!(
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = ShortName::of::<C>(),
ty_name = debug_name.shortname(),
name = name.map_or_else(
|| format!("Entity {entity}"),
|s| format!("The {s} entity")
@ -640,6 +664,29 @@ mod tests {
);
}
#[test]
fn insert_child() {
let mut world = World::new();
let child1 = world.spawn_empty().id();
let child2 = world.spawn_empty().id();
let child3 = world.spawn_empty().id();
let mut entity_world_mut = world.spawn_empty();
let first_children = entity_world_mut.add_children(&[child1, child2]);
let root = first_children.insert_child(1, child3).id();
let hierarchy = get_hierarchy(&world, root);
assert_eq!(
hierarchy,
Node::new_with(
root,
vec![Node::new(child1), Node::new(child3), Node::new(child2)]
)
);
}
// regression test for https://github.com/bevyengine/bevy/pull/19134
#[test]
fn insert_children_index_bound() {
@ -697,6 +744,25 @@ mod tests {
);
}
#[test]
fn remove_child() {
let mut world = World::new();
let child1 = world.spawn_empty().id();
let child2 = world.spawn_empty().id();
let child3 = world.spawn_empty().id();
let mut root = world.spawn_empty();
root.add_children(&[child1, child2, child3]);
root.remove_child(child2);
let root = root.id();
let hierarchy = get_hierarchy(&world, root);
assert_eq!(
hierarchy,
Node::new_with(root, vec![Node::new(child1), Node::new(child3)])
);
}
#[test]
fn self_parenting_invalid() {
let mut world = World::new();

View File

@ -41,6 +41,7 @@ pub mod event;
pub mod hierarchy;
pub mod intern;
pub mod label;
pub mod lifecycle;
pub mod name;
pub mod never;
pub mod observer;
@ -48,7 +49,6 @@ pub mod query;
#[cfg(feature = "bevy_reflect")]
pub mod reflect;
pub mod relationship;
pub mod removal_detection;
pub mod resource;
pub mod schedule;
pub mod spawn;
@ -60,13 +60,17 @@ pub mod world;
pub use bevy_ptr as ptr;
#[cfg(feature = "hotpatching")]
use event::Event;
use event::{BufferedEvent, Event};
/// The ECS prelude.
///
/// This includes the most common types in this crate, re-exported for your convenience.
pub mod prelude {
#[doc(hidden)]
#[expect(
deprecated,
reason = "`Trigger` was deprecated in favor of `On`, and `OnX` lifecycle events were deprecated in favor of `X` events."
)]
pub use crate::{
bundle::Bundle,
change_detection::{DetectChanges, DetectChangesMut, Mut, Ref},
@ -74,14 +78,19 @@ pub mod prelude {
component::Component,
entity::{ContainsEntity, Entity, EntityMapper},
error::{BevyError, Result},
event::{Event, EventMutator, EventReader, EventWriter, Events},
event::{
BufferedEvent, EntityEvent, Event, EventMutator, EventReader, EventWriter, Events,
},
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
lifecycle::{
Add, Despawn, Insert, OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, Remove,
RemovedComponents, Replace,
},
name::{Name, NameOrEntity},
observer::{Observer, Trigger},
observer::{Observer, On, Trigger},
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
related,
relationship::RelationshipTarget,
removal_detection::RemovedComponents,
resource::Resource,
schedule::{
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
@ -96,7 +105,7 @@ pub mod prelude {
},
world::{
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
FromWorld, World,
},
};
@ -130,7 +139,7 @@ pub mod __macro_exports {
///
/// Systems should refresh their inner pointers.
#[cfg(feature = "hotpatching")]
#[derive(Event, Default)]
#[derive(Event, BufferedEvent, Default)]
pub struct HotPatched;
#[cfg(test)]

View File

@ -0,0 +1,643 @@
//! 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:
//!
//! - [`Add`]: Triggered when a component is added to an entity that did not already have it.
//! - [`Insert`]: Triggered when a component is added to an entity, regardless of whether it already had it.
//!
//! When both events occur, [`Add`] hooks are evaluated before [`Insert`].
//!
//! Next, we have lifecycle events that are triggered when a component is removed from an entity:
//!
//! - [`Replace`]: Triggered when a component is removed from an entity, regardless if it is then replaced with a new value.
//! - [`Remove`]: Triggered when a component is removed from an entity and not replaced, before the component is removed.
//! - [`Despawn`]: Triggered for each component on an entity when it is despawned.
//!
//! [`Replace`] hooks are evaluated before [`Remove`], then finally [`Despawn`] hooks are evaluated.
//!
//! [`Add`] and [`Remove`] 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, [`Insert`] and [`Replace`] 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 [`Insert`] and [`Replace`] 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, [`Add`] corresponds to [`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::{
BufferedEvent, EntityEvent, Event, EventCursor, EventId, EventIterator,
EventIteratorWithId, Events,
},
query::FilteredAccessSet,
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 [`Add`]
pub const ADD: ComponentId = ComponentId::new(0);
/// [`ComponentId`] for [`Insert`]
pub const INSERT: ComponentId = ComponentId::new(1);
/// [`ComponentId`] for [`Replace`]
pub const REPLACE: ComponentId = ComponentId::new(2);
/// [`ComponentId`] for [`Remove`]
pub const REMOVE: ComponentId = ComponentId::new(3);
/// [`ComponentId`] for [`Despawn`]
pub const DESPAWN: ComponentId = ComponentId::new(4);
/// Trigger emitted when a component is inserted onto an entity that does not already have that
/// component. Runs before `Insert`.
/// See [`crate::lifecycle::ComponentHooks::on_add`] for more information.
#[derive(Event, EntityEvent, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
#[doc(alias = "OnAdd")]
pub struct Add;
/// Trigger emitted when a component is inserted, regardless of whether or not the entity already
/// had that component. Runs after `Add`, if it ran.
/// See [`crate::lifecycle::ComponentHooks::on_insert`] for more information.
#[derive(Event, EntityEvent, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
#[doc(alias = "OnInsert")]
pub struct Insert;
/// 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, EntityEvent, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
#[doc(alias = "OnReplace")]
pub struct Replace;
/// 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, EntityEvent, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
#[doc(alias = "OnRemove")]
pub struct Remove;
/// Trigger emitted for each component on an entity when it is despawned.
/// See [`crate::lifecycle::ComponentHooks::on_despawn`] for more information.
#[derive(Event, EntityEvent, Debug, Clone)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
#[doc(alias = "OnDespawn")]
pub struct Despawn;
/// Deprecated in favor of [`Add`].
#[deprecated(since = "0.17.0", note = "Renamed to `Add`.")]
pub type OnAdd = Add;
/// Deprecated in favor of [`Insert`].
#[deprecated(since = "0.17.0", note = "Renamed to `Insert`.")]
pub type OnInsert = Insert;
/// Deprecated in favor of [`Replace`].
#[deprecated(since = "0.17.0", note = "Renamed to `Replace`.")]
pub type OnReplace = Replace;
/// Deprecated in favor of [`Remove`].
#[deprecated(since = "0.17.0", note = "Renamed to `Remove`.")]
pub type OnRemove = Remove;
/// Deprecated in favor of [`Despawn`].
#[deprecated(since = "0.17.0", note = "Renamed to `Despawn`.")]
pub type OnDespawn = Despawn;
/// Wrapper around [`Entity`] for [`RemovedComponents`].
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
#[derive(Event, BufferedEvent, 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) -> Self::State {}
fn init_access(
_state: &Self::State,
_system_meta: &mut SystemMeta,
_component_access_set: &mut FilteredAccessSet<ComponentId>,
_world: &mut World,
) {
}
#[inline]
unsafe fn get_param<'w, 's>(
_state: &'s mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'w>,
_change_tick: Tick,
) -> Self::Item<'w, 's> {
world.removed_components()
}
}

View File

@ -141,7 +141,7 @@ pub struct NameOrEntity {
pub entity: Entity,
}
impl<'a> core::fmt::Display for NameOrEntityItem<'a> {
impl<'w, 's> core::fmt::Display for NameOrEntityItem<'w, 's> {
#[inline(always)]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self.name {
@ -274,9 +274,9 @@ mod tests {
let e2 = world.spawn(name.clone()).id();
let mut query = world.query::<NameOrEntity>();
let d1 = query.get(&world, e1).unwrap();
let d2 = query.get(&world, e2).unwrap();
// NameOrEntity Display for entities without a Name should be {index}v{generation}
assert_eq!(d1.to_string(), "0v0");
let d2 = query.get(&world, e2).unwrap();
// NameOrEntity Display for entities with a Name should be the Name
assert_eq!(d2.to_string(), "MyName");
}

View File

@ -1,18 +1,29 @@
use crate::{
component::{
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
},
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
lifecycle::{ComponentHook, HookContext},
world::World,
};
use alloc::vec::Vec;
#[cfg(feature = "bevy_reflect")]
use crate::prelude::ReflectComponent;
use super::Observer;
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
#[derive(Default)]
#[derive(Default, Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect(Component, Debug))]
pub struct ObservedBy(pub(crate) Vec<Entity>);
impl ObservedBy {
/// Provides a read-only reference to the list of entities observing this entity.
pub fn get(&self) -> &[Entity] {
&self.0
}
}
impl Component for ObservedBy {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
@ -86,7 +97,7 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
let event_types = observer_state.descriptor.events.clone();
let components = observer_state.descriptor.components.clone();
for event_type in event_types {
let observers = world.observers.get_observers(event_type);
let observers = world.observers.get_observers_mut(event_type);
if components.is_empty() {
if let Some(map) = observers.entity_observers.get(&source).cloned() {
observers.entity_observers.insert(target, map);
@ -97,8 +108,10 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
else {
continue;
};
if let Some(map) = observers.entity_map.get(&source).cloned() {
observers.entity_map.insert(target, map);
if let Some(map) =
observers.entity_component_observers.get(&source).cloned()
{
observers.entity_component_observers.insert(target, map);
}
}
}
@ -110,14 +123,18 @@ fn component_clone_observed_by(_source: &SourceComponent, ctx: &mut ComponentClo
#[cfg(test)]
mod tests {
use crate::{
entity::EntityCloner, event::Event, observer::Trigger, resource::Resource, system::ResMut,
entity::EntityCloner,
event::{EntityEvent, Event},
observer::On,
resource::Resource,
system::ResMut,
world::World,
};
#[derive(Resource, Default)]
struct Num(usize);
#[derive(Event)]
#[derive(Event, EntityEvent)]
struct E;
#[test]
@ -127,7 +144,7 @@ mod tests {
let e = world
.spawn_empty()
.observe(|_: Trigger<E>, mut res: ResMut<Num>| res.0 += 1)
.observe(|_: On<E>, mut res: ResMut<Num>| res.0 += 1)
.id();
world.flush();

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,11 @@
use alloc::{boxed::Box, vec};
use bevy_utils::prelude::DebugName;
use core::any::Any;
use crate::{
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
component::{ComponentId, Mutable, StorageType},
error::{ErrorContext, ErrorHandler},
lifecycle::{ComponentHook, HookContext},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
@ -20,15 +22,16 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer".
///
/// Observers listen for a "trigger" of a specific [`Event`]. Events are triggered by calling [`World::trigger`] or [`World::trigger_targets`].
/// Observers listen for a "trigger" of a specific [`Event`]. An event can be triggered on the [`World`]
/// by calling [`World::trigger`], or if the event is an [`EntityEvent`], it can also be triggered for specific
/// entity targets using [`World::trigger_targets`].
///
/// Note that "buffered" events sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered. They must be triggered at a specific
/// point in the schedule.
/// Note that [`BufferedEvent`]s sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered.
/// They must be triggered at a specific point in the schedule.
///
/// # Usage
///
/// The simplest usage
/// of the observer pattern looks like this:
/// The simplest usage of the observer pattern looks like this:
///
/// ```
/// # use bevy_ecs::prelude::*;
@ -38,7 +41,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// message: String,
/// }
///
/// world.add_observer(|trigger: Trigger<Speak>| {
/// world.add_observer(|trigger: On<Speak>| {
/// println!("{}", trigger.event().message);
/// });
///
@ -59,8 +62,8 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # #[derive(Event)]
/// # struct Speak;
/// // These are functionally the same:
/// world.add_observer(|trigger: Trigger<Speak>| {});
/// world.spawn(Observer::new(|trigger: Trigger<Speak>| {}));
/// world.add_observer(|trigger: On<Speak>| {});
/// world.spawn(Observer::new(|trigger: On<Speak>| {}));
/// ```
///
/// Observers are systems. They can access arbitrary [`World`] data by adding [`SystemParam`]s:
@ -72,14 +75,14 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # struct PrintNames;
/// # #[derive(Component, Debug)]
/// # struct Name;
/// world.add_observer(|trigger: Trigger<PrintNames>, names: Query<&Name>| {
/// world.add_observer(|trigger: On<PrintNames>, names: Query<&Name>| {
/// for name in &names {
/// println!("{name:?}");
/// }
/// });
/// ```
///
/// Note that [`Trigger`] must always be the first parameter.
/// Note that [`On`] must always be the first parameter.
///
/// You can also add [`Commands`], which means you can spawn new entities, insert new components, etc:
///
@ -90,7 +93,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # struct SpawnThing;
/// # #[derive(Component, Debug)]
/// # struct Thing;
/// world.add_observer(|trigger: Trigger<SpawnThing>, mut commands: Commands| {
/// world.add_observer(|trigger: On<SpawnThing>, mut commands: Commands| {
/// commands.spawn(Thing);
/// });
/// ```
@ -104,7 +107,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # struct A;
/// # #[derive(Event)]
/// # struct B;
/// world.add_observer(|trigger: Trigger<A>, mut commands: Commands| {
/// world.add_observer(|trigger: On<A>, mut commands: Commands| {
/// commands.trigger(B);
/// });
/// ```
@ -113,16 +116,17 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// recursively evaluated until there are no commands left, meaning nested triggers all
/// evaluate at the same time!
///
/// Events can be triggered for entities, which will be passed to the [`Observer`]:
/// If the event is an [`EntityEvent`], it can be triggered for specific entities,
/// which will be passed to the [`Observer`]:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// #[derive(Event)]
/// #[derive(Event, EntityEvent)]
/// struct Explode;
///
/// world.add_observer(|trigger: Trigger<Explode>, mut commands: Commands| {
/// world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
/// println!("Entity {} goes BOOM!", trigger.target());
/// commands.entity(trigger.target()).despawn();
/// });
@ -139,7 +143,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event)]
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// world.trigger_targets(Explode, [e1, e2]);
/// ```
@ -153,14 +157,14 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event)]
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// world.entity_mut(e1).observe(|trigger: On<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.target()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// world.entity_mut(e2).observe(|trigger: On<Explode>, mut commands: Commands| {
/// println!("The explosion fizzles! This entity is immune!");
/// });
/// ```
@ -175,9 +179,9 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// # #[derive(Event)]
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// let mut observer = Observer::new(|trigger: Trigger<Explode>| {});
/// let mut observer = Observer::new(|trigger: On<Explode>| {});
/// observer.watch_entity(entity);
/// world.spawn(observer);
/// ```
@ -191,7 +195,7 @@ pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate:
pub struct Observer {
hook_on_add: ComponentHook,
error_handler: Option<ErrorHandler>,
system: Box<dyn Any + Send + Sync + 'static>,
system: Box<dyn AnyNamedSystem>,
pub(crate) descriptor: ObserverDescriptor,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
@ -229,7 +233,7 @@ impl Observer {
/// Creates a new [`Observer`] with custom runner, this is mostly used for dynamic event observer
pub fn with_dynamic_runner(runner: ObserverRunner) -> Self {
Self {
system: Box::new(|| {}),
system: Box::new(IntoSystem::into_system(|| {})),
descriptor: Default::default(),
hook_on_add: |mut world, hook_context| {
let default_error_handler = world.default_error_handler();
@ -296,6 +300,11 @@ impl Observer {
pub fn descriptor(&self) -> &ObserverDescriptor {
&self.descriptor
}
/// Returns the name of the [`Observer`]'s system .
pub fn system_name(&self) -> DebugName {
self.system.system_name()
}
}
impl Component for Observer {
@ -350,7 +359,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
}
state.last_trigger_id = last_trigger;
let trigger: Trigger<E, B> = Trigger::new(
let trigger: On<E, B> = On::new(
// SAFETY: Caller ensures `ptr` is castable to `&mut T`
unsafe { ptr.deref_mut() },
propagate,
@ -361,7 +370,8 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
// - observer was triggered so must have an `Observer` component.
// - observer cannot be dropped or mutated until after the system pointer is already dropped.
let system: *mut dyn ObserverSystem<E, B> = unsafe {
let system = state.system.downcast_mut::<S>().debug_checked_unwrap();
let system: &mut dyn Any = state.system.as_mut();
let system = system.downcast_mut::<S>().debug_checked_unwrap();
&mut *system
};
@ -410,7 +420,17 @@ 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`).
trait AnyNamedSystem: Any + Send + Sync + 'static {
fn system_name(&self) -> DebugName;
}
impl<T: Any + System> AnyNamedSystem for T {
fn system_name(&self) -> DebugName {
self.name()
}
}
/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::lifecycle::ComponentHooks::on_add`).
///
/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters
/// erased.
@ -428,11 +448,12 @@ fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
B::component_ids(&mut world.components_registrator(), &mut |id| {
components.push(id);
});
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
observe.descriptor.events.push(event_id);
observe.descriptor.components.extend(components);
if let Some(mut observer) = world.get_mut::<Observer>(entity) {
observer.descriptor.events.push(event_id);
observer.descriptor.components.extend(components);
let system: *mut dyn ObserverSystem<E, B> = observe.system.downcast_mut::<S>().unwrap();
let system: &mut dyn Any = observer.system.as_mut();
let system: *mut dyn ObserverSystem<E, B> = system.downcast_mut::<S>().unwrap();
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
unsafe {
(*system).initialize(world);
@ -447,7 +468,7 @@ mod tests {
use crate::{
error::{ignore, DefaultErrorHandler},
event::Event,
observer::Trigger,
observer::On,
};
#[derive(Event)]
@ -456,7 +477,7 @@ mod tests {
#[test]
#[should_panic(expected = "I failed!")]
fn test_fallible_observer() {
fn system(_: Trigger<TriggerEvent>) -> Result {
fn system(_: On<TriggerEvent>) -> Result {
Err("I failed!".into())
}
@ -471,7 +492,7 @@ mod tests {
#[derive(Resource, Default)]
struct Ran(bool);
fn system(_: Trigger<TriggerEvent>, mut ran: ResMut<Ran>) -> Result {
fn system(_: On<TriggerEvent>, mut ran: ResMut<Ran>) -> Result {
ran.0 = true;
Err("I failed!".into())
}
@ -499,7 +520,7 @@ mod tests {
expected = "Exclusive system `bevy_ecs::observer::runner::tests::exclusive_system_cannot_be_observer::system` may not be used as observer.\nInstead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
)]
fn exclusive_system_cannot_be_observer() {
fn system(_: Trigger<TriggerEvent>, _world: &mut World) {}
fn system(_: On<TriggerEvent>, _world: &mut World) {}
let mut world = World::default();
world.add_observer(system);
}

View File

@ -4,7 +4,6 @@ use crate::world::World;
use alloc::{format, string::String, vec, vec::Vec};
use core::{fmt, fmt::Debug, marker::PhantomData};
use derive_more::From;
use disqualified::ShortName;
use fixedbitset::FixedBitSet;
use thiserror::Error;
@ -999,12 +998,11 @@ impl AccessConflicts {
.map(|index| {
format!(
"{}",
ShortName(
&world
.components
.get_name(ComponentId::get_sparse_set_index(index))
.unwrap()
)
world
.components
.get_name(ComponentId::get_sparse_set_index(index))
.unwrap()
.shortname()
)
})
.collect::<Vec<_>>()

View File

@ -1,3 +1,4 @@
use bevy_utils::prelude::DebugName;
use thiserror::Error;
use crate::{
@ -54,10 +55,10 @@ impl core::fmt::Display for QueryEntityError {
pub enum QuerySingleError {
/// No entity fits the query.
#[error("No entities fit the query {0}")]
NoEntities(&'static str),
NoEntities(DebugName),
/// Multiple entities fit the query.
#[error("Multiple entities fit the query {0}")]
MultipleEntities(&'static str),
MultipleEntities(DebugName),
}
#[cfg(test)]

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ use crate::{
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::prelude::DebugName;
use core::{cell::UnsafeCell, marker::PhantomData};
use variadics_please::all_tuples;
@ -103,6 +104,7 @@ pub unsafe trait QueryFilter: WorldQuery {
/// Must always be called _after_ [`WorldQuery::set_table`] or [`WorldQuery::set_archetype`]. `entity` and
/// `table_row` must be in the range of the current table and archetype.
unsafe fn filter_fetch(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
@ -204,6 +206,7 @@ unsafe impl<T: Component> QueryFilter for With<T> {
#[inline(always)]
unsafe fn filter_fetch(
_state: &Self::State,
_fetch: &mut Self::Fetch<'_>,
_entity: Entity,
_table_row: TableRow,
@ -304,6 +307,7 @@ unsafe impl<T: Component> QueryFilter for Without<T> {
#[inline(always)]
unsafe fn filter_fetch(
_state: &Self::State,
_fetch: &mut Self::Fetch<'_>,
_entity: Entity,
_table_row: TableRow,
@ -400,7 +404,7 @@ macro_rules! impl_or_query_filter {
const IS_DENSE: bool = true $(&& $filter::IS_DENSE)*;
#[inline]
unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> {
unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> {
let ($($filter,)*) = state;
($(OrFetch {
// SAFETY: The invariants are upheld by the caller.
@ -410,7 +414,7 @@ macro_rules! impl_or_query_filter {
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table) {
let ($($filter,)*) = fetch;
let ($($state,)*) = state;
$(
@ -423,9 +427,9 @@ macro_rules! impl_or_query_filter {
}
#[inline]
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
state: & Self::State,
state: &'s Self::State,
archetype: &'w Archetype,
table: &'w Table
) {
@ -495,20 +499,22 @@ macro_rules! impl_or_query_filter {
#[inline(always)]
unsafe fn filter_fetch(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow
) -> bool {
let ($($state,)*) = state;
let ($($filter,)*) = fetch;
// SAFETY: The invariants are upheld by the caller.
false $(|| ($filter.matches && unsafe { $filter::filter_fetch(&mut $filter.fetch, entity, table_row) }))*
false $(|| ($filter.matches && unsafe { $filter::filter_fetch($state, &mut $filter.fetch, entity, table_row) }))*
}
}
};
}
macro_rules! impl_tuple_query_filter {
($(#[$meta:meta])* $($name: ident),*) => {
($(#[$meta:meta])* $(($name: ident, $state: ident)),*) => {
#[expect(
clippy::allow_attributes,
reason = "This is a tuple-related macro; as such the lints below may not always apply."
@ -528,13 +534,15 @@ macro_rules! impl_tuple_query_filter {
#[inline(always)]
unsafe fn filter_fetch(
state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow
) -> bool {
let ($($state,)*) = state;
let ($($name,)*) = fetch;
// SAFETY: The invariants are upheld by the caller.
true $(&& unsafe { $name::filter_fetch($name, entity, table_row) })*
true $(&& unsafe { $name::filter_fetch($state, $name, entity, table_row) })*
}
}
@ -546,7 +554,8 @@ all_tuples!(
impl_tuple_query_filter,
0,
15,
F
F,
S
);
all_tuples!(
#[doc(fake_variadic)]
@ -609,7 +618,12 @@ unsafe impl<T: Component> QueryFilter for Allows<T> {
const IS_ARCHETYPAL: bool = true;
#[inline(always)]
unsafe fn filter_fetch(_: &mut Self::Fetch<'_>, _: Entity, _: TableRow) -> bool {
unsafe fn filter_fetch(
_: &Self::State,
_: &mut Self::Fetch<'_>,
_: Entity,
_: TableRow,
) -> bool {
true
}
}
@ -718,9 +732,9 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
}
#[inline]
unsafe fn init_fetch<'w>(
unsafe fn init_fetch<'w, 's>(
world: UnsafeWorldCell<'w>,
&id: &ComponentId,
&id: &'s ComponentId,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
@ -748,9 +762,9 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
};
#[inline]
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
component_id: &ComponentId,
component_id: &'s ComponentId,
_archetype: &'w Archetype,
table: &'w Table,
) {
@ -763,9 +777,9 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
}
#[inline]
unsafe fn set_table<'w>(
unsafe fn set_table<'w, 's>(
fetch: &mut Self::Fetch<'w>,
&component_id: &ComponentId,
&component_id: &'s ComponentId,
table: &'w Table,
) {
let table_ticks = Some(
@ -781,7 +795,7 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
#[inline]
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) {
if access.access().has_component_write(id) {
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",core::any::type_name::<T>());
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::<T>());
}
access.add_component_read(id);
}
@ -807,6 +821,7 @@ unsafe impl<T: Component> QueryFilter for Added<T> {
const IS_ARCHETYPAL: bool = false;
#[inline(always)]
unsafe fn filter_fetch(
_state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
@ -944,9 +959,9 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
}
#[inline]
unsafe fn init_fetch<'w>(
unsafe fn init_fetch<'w, 's>(
world: UnsafeWorldCell<'w>,
&id: &ComponentId,
&id: &'s ComponentId,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
@ -974,9 +989,9 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
};
#[inline]
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
component_id: &ComponentId,
component_id: &'s ComponentId,
_archetype: &'w Archetype,
table: &'w Table,
) {
@ -989,9 +1004,9 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
}
#[inline]
unsafe fn set_table<'w>(
unsafe fn set_table<'w, 's>(
fetch: &mut Self::Fetch<'w>,
&component_id: &ComponentId,
&component_id: &'s ComponentId,
table: &'w Table,
) {
let table_ticks = Some(
@ -1007,7 +1022,7 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
#[inline]
fn update_component_access(&id: &ComponentId, access: &mut FilteredAccess<ComponentId>) {
if access.access().has_component_write(id) {
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.",core::any::type_name::<T>());
panic!("$state_name<{}> conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", DebugName::type_name::<T>());
}
access.add_component_read(id);
}
@ -1034,6 +1049,7 @@ unsafe impl<T: Component> QueryFilter for Changed<T> {
#[inline(always)]
unsafe fn filter_fetch(
_state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
@ -1141,9 +1157,9 @@ unsafe impl WorldQuery for Spawned {
}
#[inline]
unsafe fn init_fetch<'w>(
unsafe fn init_fetch<'w, 's>(
world: UnsafeWorldCell<'w>,
_state: &(),
_state: &'s (),
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w> {
@ -1157,16 +1173,16 @@ unsafe impl WorldQuery for Spawned {
const IS_DENSE: bool = true;
#[inline]
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
_fetch: &mut Self::Fetch<'w>,
_state: &(),
_state: &'s (),
_archetype: &'w Archetype,
_table: &'w Table,
) {
}
#[inline]
unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &(), _table: &'w Table) {}
unsafe fn set_table<'w, 's>(_fetch: &mut Self::Fetch<'w>, _state: &'s (), _table: &'w Table) {}
#[inline]
fn update_component_access(_state: &(), _access: &mut FilteredAccess<ComponentId>) {}
@ -1188,6 +1204,7 @@ unsafe impl QueryFilter for Spawned {
#[inline(always)]
unsafe fn filter_fetch(
_state: &Self::State,
fetch: &mut Self::Fetch<'_>,
entity: Entity,
_table_row: TableRow,

View File

@ -140,7 +140,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
range: Option<Range<u32>>,
) -> B
where
Func: FnMut(B, D::Item<'w>) -> B,
Func: FnMut(B, D::Item<'w, 's>) -> B,
{
if self.cursor.is_dense {
// SAFETY: `self.cursor.is_dense` is true, so storage ids are guaranteed to be table ids.
@ -203,7 +203,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
rows: Range<u32>,
) -> B
where
Func: FnMut(B, D::Item<'w>) -> B,
Func: FnMut(B, D::Item<'w, 's>) -> B,
{
if table.is_empty() {
return accum;
@ -225,14 +225,26 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
// SAFETY: set_table was called prior.
// Caller assures `row` in range of the current archetype.
let fetched = unsafe { !F::filter_fetch(&mut self.cursor.filter, *entity, row) };
let fetched = unsafe {
!F::filter_fetch(
&self.query_state.filter_state,
&mut self.cursor.filter,
*entity,
row,
)
};
if fetched {
continue;
}
// SAFETY: set_table was called prior.
// Caller assures `row` in range of the current archetype.
let item = D::fetch(&mut self.cursor.fetch, *entity, row);
let item = D::fetch(
&self.query_state.fetch_state,
&mut self.cursor.fetch,
*entity,
row,
);
accum = func(accum, item);
}
@ -255,7 +267,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
indices: Range<u32>,
) -> B
where
Func: FnMut(B, D::Item<'w>) -> B,
Func: FnMut(B, D::Item<'w, 's>) -> B,
{
if archetype.is_empty() {
return accum;
@ -283,6 +295,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
// Caller assures `index` in range of the current archetype.
let fetched = unsafe {
!F::filter_fetch(
&self.query_state.filter_state,
&mut self.cursor.filter,
archetype_entity.id(),
archetype_entity.table_row(),
@ -296,6 +309,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
// Caller assures `index` in range of the current archetype.
let item = unsafe {
D::fetch(
&self.query_state.fetch_state,
&mut self.cursor.fetch,
archetype_entity.id(),
archetype_entity.table_row(),
@ -324,7 +338,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
rows: Range<u32>,
) -> B
where
Func: FnMut(B, D::Item<'w>) -> B,
Func: FnMut(B, D::Item<'w, 's>) -> B,
{
if archetype.is_empty() {
return accum;
@ -356,14 +370,26 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
// SAFETY: set_table was called prior.
// Caller assures `row` in range of the current archetype.
let filter_matched = unsafe { F::filter_fetch(&mut self.cursor.filter, entity, row) };
let filter_matched = unsafe {
F::filter_fetch(
&self.query_state.filter_state,
&mut self.cursor.filter,
entity,
row,
)
};
if !filter_matched {
continue;
}
// SAFETY: set_table was called prior.
// Caller assures `row` in range of the current archetype.
let item = D::fetch(&mut self.cursor.fetch, entity, row);
let item = D::fetch(
&self.query_state.fetch_state,
&mut self.cursor.fetch,
entity,
row,
);
accum = func(accum, item);
}
@ -492,7 +518,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
impl ExactSizeIterator<Item = Entity> + DoubleEndedIterator + FusedIterator + 'w,
>
where
for<'lw> L::Item<'lw>: Ord,
for<'lw, 'ls> L::Item<'lw, 'ls>: Ord,
{
self.sort_impl::<L>(|keyed_query| keyed_query.sort())
}
@ -549,7 +575,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
impl ExactSizeIterator<Item = Entity> + DoubleEndedIterator + FusedIterator + 'w,
>
where
for<'lw> L::Item<'lw>: Ord,
for<'lw, 'ls> L::Item<'lw, 'ls>: Ord,
{
self.sort_impl::<L>(|keyed_query| keyed_query.sort_unstable())
}
@ -605,7 +631,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
/// ```
pub fn sort_by<L: ReadOnlyQueryData + 'w>(
self,
mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering,
mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering,
) -> QuerySortedIter<
'w,
's,
@ -637,7 +663,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
/// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty.
pub fn sort_unstable_by<L: ReadOnlyQueryData + 'w>(
self,
mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering,
mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering,
) -> QuerySortedIter<
'w,
's,
@ -729,7 +755,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
/// ```
pub fn sort_by_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'_>) -> K,
mut f: impl FnMut(&L::Item<'_, '_>) -> K,
) -> QuerySortedIter<
'w,
's,
@ -762,7 +788,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
/// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty.
pub fn sort_unstable_by_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'_>) -> K,
mut f: impl FnMut(&L::Item<'_, '_>) -> K,
) -> QuerySortedIter<
'w,
's,
@ -797,7 +823,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
/// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty.
pub fn sort_by_cached_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'_>) -> K,
mut f: impl FnMut(&L::Item<'_, '_>) -> K,
) -> QuerySortedIter<
'w,
's,
@ -827,7 +853,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
/// This will panic if `next` has been called on `QueryIter` before, unless the underlying `Query` is empty.
fn sort_impl<L: ReadOnlyQueryData + 'w>(
self,
f: impl FnOnce(&mut Vec<(L::Item<'_>, NeutralOrd<Entity>)>),
f: impl FnOnce(&mut Vec<(L::Item<'_, '_>, NeutralOrd<Entity>)>),
) -> QuerySortedIter<
'w,
's,
@ -856,7 +882,11 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
.map(|(key, entity)| (key, NeutralOrd(entity)))
.collect();
f(&mut keyed_query);
let entity_iter = keyed_query.into_iter().map(|(.., entity)| entity.0);
let entity_iter = keyed_query
.into_iter()
.map(|(.., entity)| entity.0)
.collect::<Vec<_>>()
.into_iter();
// SAFETY:
// `self.world` has permission to access the required components.
// Each lens query item is dropped before the respective actual query item is accessed.
@ -873,7 +903,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIter<'w, 's, D, F> {
}
impl<'w, 's, D: QueryData, F: QueryFilter> Iterator for QueryIter<'w, 's, D, F> {
type Item = D::Item<'w>;
type Item = D::Item<'w, 's>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
@ -1010,7 +1040,7 @@ where
/// # Safety
/// `entity` must stem from `self.entity_iter`, and not have been passed before.
#[inline(always)]
unsafe fn fetch_next(&mut self, entity: Entity) -> D::Item<'w> {
unsafe fn fetch_next(&mut self, entity: Entity) -> D::Item<'w, 's> {
let (location, archetype, table);
// SAFETY:
// `tables` and `archetypes` belong to the same world that the [`QueryIter`]
@ -1039,7 +1069,14 @@ where
// SAFETY:
// - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
// - fetch is only called once for each entity.
unsafe { D::fetch(&mut self.fetch, entity, location.table_row) }
unsafe {
D::fetch(
&self.query_state.fetch_state,
&mut self.fetch,
entity,
location.table_row,
)
}
}
}
@ -1048,7 +1085,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator> Iterator
where
I: Iterator<Item = Entity>,
{
type Item = D::Item<'w>;
type Item = D::Item<'w, 's>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
@ -1170,7 +1207,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
fetch: &mut D::Fetch<'w>,
filter: &mut F::Fetch<'w>,
query_state: &'s QueryState<D, F>,
) -> Option<D::Item<'w>> {
) -> Option<D::Item<'w, 's>> {
for entity_borrow in entity_iter {
let entity = entity_borrow.entity();
let Some(location) = entities.get(entity) else {
@ -1200,11 +1237,20 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
// SAFETY: set_archetype was called prior.
// `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
if unsafe { F::filter_fetch(filter, entity, location.table_row) } {
if unsafe {
F::filter_fetch(
&query_state.filter_state,
filter,
entity,
location.table_row,
)
} {
// SAFETY:
// - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
// - fetch is only called once for each entity.
return Some(unsafe { D::fetch(fetch, entity, location.table_row) });
return Some(unsafe {
D::fetch(&query_state.fetch_state, fetch, entity, location.table_row)
});
}
}
None
@ -1212,7 +1258,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// Get next result from the query
#[inline(always)]
pub fn fetch_next(&mut self) -> Option<D::Item<'_>> {
pub fn fetch_next(&mut self) -> Option<D::Item<'_, 's>> {
// SAFETY:
// All arguments stem from self.
// We are limiting the returned reference to self,
@ -1336,7 +1382,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
impl ExactSizeIterator<Item = Entity> + DoubleEndedIterator + FusedIterator + 'w,
>
where
for<'lw> L::Item<'lw>: Ord,
for<'lw, 'ls> L::Item<'lw, 'ls>: Ord,
{
self.sort_impl::<L>(|keyed_query| keyed_query.sort())
}
@ -1394,7 +1440,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
impl ExactSizeIterator<Item = Entity> + DoubleEndedIterator + FusedIterator + 'w,
>
where
for<'lw> L::Item<'lw>: Ord,
for<'lw, 'ls> L::Item<'lw, 'ls>: Ord,
{
self.sort_impl::<L>(|keyed_query| keyed_query.sort_unstable())
}
@ -1451,7 +1497,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// ```
pub fn sort_by<L: ReadOnlyQueryData + 'w>(
self,
mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering,
mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering,
) -> QuerySortedManyIter<
'w,
's,
@ -1482,7 +1528,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// called on [`QueryManyIter`] before.
pub fn sort_unstable_by<L: ReadOnlyQueryData + 'w>(
self,
mut compare: impl FnMut(&L::Item<'_>, &L::Item<'_>) -> Ordering,
mut compare: impl FnMut(&L::Item<'_, '_>, &L::Item<'_, '_>) -> Ordering,
) -> QuerySortedManyIter<
'w,
's,
@ -1576,7 +1622,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// ```
pub fn sort_by_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'_>) -> K,
mut f: impl FnMut(&L::Item<'_, '_>) -> K,
) -> QuerySortedManyIter<
'w,
's,
@ -1608,7 +1654,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// called on [`QueryManyIter`] before.
pub fn sort_unstable_by_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'_>) -> K,
mut f: impl FnMut(&L::Item<'_, '_>) -> K,
) -> QuerySortedManyIter<
'w,
's,
@ -1642,7 +1688,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// called on [`QueryManyIter`] before.
pub fn sort_by_cached_key<L: ReadOnlyQueryData + 'w, K>(
self,
mut f: impl FnMut(&L::Item<'_>) -> K,
mut f: impl FnMut(&L::Item<'_, '_>) -> K,
) -> QuerySortedManyIter<
'w,
's,
@ -1671,7 +1717,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>>
/// called on [`QueryManyIter`] before.
fn sort_impl<L: ReadOnlyQueryData + 'w>(
self,
f: impl FnOnce(&mut Vec<(L::Item<'_>, NeutralOrd<Entity>)>),
f: impl FnOnce(&mut Vec<(L::Item<'_, '_>, NeutralOrd<Entity>)>),
) -> QuerySortedManyIter<
'w,
's,
@ -1721,7 +1767,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator<Item: EntityEq
{
/// Get next result from the back of the query
#[inline(always)]
pub fn fetch_next_back(&mut self) -> Option<D::Item<'_>> {
pub fn fetch_next_back(&mut self) -> Option<D::Item<'_, 's>> {
// SAFETY:
// All arguments stem from self.
// We are limiting the returned reference to self,
@ -1745,7 +1791,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator<Item: EntityEq
impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator<Item: EntityEquivalent>> Iterator
for QueryManyIter<'w, 's, D, F, I>
{
type Item = D::Item<'w>;
type Item = D::Item<'w, 's>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
@ -1861,7 +1907,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator>
impl<'w, 's, D: QueryData, F: QueryFilter, I: EntitySetIterator> Iterator
for QueryManyUniqueIter<'w, 's, D, F, I>
{
type Item = D::Item<'w>;
type Item = D::Item<'w, 's>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
@ -1954,7 +2000,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item = Entity>>
/// It is always safe for shared access.
/// `entity` must stem from `self.entity_iter`, and not have been passed before.
#[inline(always)]
unsafe fn fetch_next_aliased_unchecked(&mut self, entity: Entity) -> D::Item<'w> {
unsafe fn fetch_next_aliased_unchecked(&mut self, entity: Entity) -> D::Item<'w, 's> {
let (location, archetype, table);
// SAFETY:
// `tables` and `archetypes` belong to the same world that the [`QueryIter`]
@ -1983,12 +2029,19 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: Iterator<Item = Entity>>
// SAFETY:
// - set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
// - fetch is only called once for each entity.
unsafe { D::fetch(&mut self.fetch, entity, location.table_row) }
unsafe {
D::fetch(
&self.query_state.fetch_state,
&mut self.fetch,
entity,
location.table_row,
)
}
}
/// Get next result from the query
#[inline(always)]
pub fn fetch_next(&mut self) -> Option<D::Item<'_>> {
pub fn fetch_next(&mut self) -> Option<D::Item<'_, 's>> {
let entity = self.entity_iter.next()?;
// SAFETY:
@ -2007,7 +2060,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator<Item = Entity>
{
/// Get next result from the query
#[inline(always)]
pub fn fetch_next_back(&mut self) -> Option<D::Item<'_>> {
pub fn fetch_next_back(&mut self) -> Option<D::Item<'_, 's>> {
let entity = self.entity_iter.next_back()?;
// SAFETY:
@ -2024,7 +2077,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, I: DoubleEndedIterator<Item = Entity>
impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, I: Iterator<Item = Entity>> Iterator
for QuerySortedManyIter<'w, 's, D, F, I>
{
type Item = D::Item<'w>;
type Item = D::Item<'w, 's>;
#[inline(always)]
fn next(&mut self) -> Option<Self::Item> {
@ -2185,7 +2238,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<
/// .
/// It is always safe for shared access.
#[inline]
unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<[D::Item<'w>; K]> {
unsafe fn fetch_next_aliased_unchecked(&mut self) -> Option<[D::Item<'w, 's>; K]> {
// PERF: can speed up the following code using `cursor.remaining()` instead of `next_item.is_none()`
// when D::IS_ARCHETYPAL && F::IS_ARCHETYPAL
//
@ -2211,11 +2264,12 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<
}
}
let mut values = MaybeUninit::<[D::Item<'w>; K]>::uninit();
let mut values = MaybeUninit::<[D::Item<'w, 's>; K]>::uninit();
let ptr = values.as_mut_ptr().cast::<D::Item<'w>>();
let ptr = values.as_mut_ptr().cast::<D::Item<'w, 's>>();
for (offset, cursor) in self.cursors.iter_mut().enumerate() {
ptr.add(offset).write(cursor.peek_last().unwrap());
ptr.add(offset)
.write(cursor.peek_last(self.query_state).unwrap());
}
Some(values.assume_init())
@ -2223,7 +2277,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<
/// Get next combination of queried components
#[inline]
pub fn fetch_next(&mut self) -> Option<[D::Item<'_>; K]> {
pub fn fetch_next(&mut self) -> Option<[D::Item<'_, 's>; K]> {
// SAFETY: we are limiting the returned reference to self,
// making sure this method cannot be called multiple times without getting rid
// of any previously returned unique references first, thus preventing aliasing.
@ -2240,7 +2294,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, const K: usize> QueryCombinationIter<
impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, const K: usize> Iterator
for QueryCombinationIter<'w, 's, D, F, K>
{
type Item = [D::Item<'w>; K];
type Item = [D::Item<'w, 's>; K];
#[inline]
fn next(&mut self) -> Option<Self::Item> {
@ -2390,7 +2444,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
/// The result of `next` and any previous calls to `peek_last` with this row must have been
/// dropped to prevent aliasing mutable references.
#[inline]
unsafe fn peek_last(&mut self) -> Option<D::Item<'w>> {
unsafe fn peek_last(&mut self, query_state: &'s QueryState<D, F>) -> Option<D::Item<'w, 's>> {
if self.current_row > 0 {
let index = self.current_row - 1;
if self.is_dense {
@ -2401,6 +2455,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
// - `*entity` and `index` are in the current table.
unsafe {
Some(D::fetch(
&query_state.fetch_state,
&mut self.fetch,
*entity,
// SAFETY: This is from an exclusive range, so it can't be max.
@ -2416,6 +2471,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
// - `archetype_entity.id()` and `archetype_entity.table_row()` are in the current archetype.
unsafe {
Some(D::fetch(
&query_state.fetch_state,
&mut self.fetch,
archetype_entity.id(),
archetype_entity.table_row(),
@ -2457,7 +2513,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
tables: &'w Tables,
archetypes: &'w Archetypes,
query_state: &'s QueryState<D, F>,
) -> Option<D::Item<'w>> {
) -> Option<D::Item<'w, 's>> {
if self.is_dense {
loop {
// we are on the beginning of the query, or finished processing a table, so skip to the next
@ -2484,7 +2540,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
unsafe { self.table_entities.get_unchecked(self.current_row as usize) };
// SAFETY: The row is less than the u32 len, so it must not be max.
let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(self.current_row)) };
if !F::filter_fetch(&mut self.filter, *entity, row) {
if !F::filter_fetch(&query_state.filter_state, &mut self.filter, *entity, row) {
self.current_row += 1;
continue;
}
@ -2494,7 +2550,8 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
// - `current_row` must be a table row in range of the current table,
// because if it was not, then the above would have been executed.
// - fetch is only called once for each `entity`.
let item = unsafe { D::fetch(&mut self.fetch, *entity, row) };
let item =
unsafe { D::fetch(&query_state.fetch_state, &mut self.fetch, *entity, row) };
self.current_row += 1;
return Some(item);
@ -2536,6 +2593,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
.get_unchecked(self.current_row as usize)
};
if !F::filter_fetch(
&query_state.filter_state,
&mut self.filter,
archetype_entity.id(),
archetype_entity.table_row(),
@ -2551,6 +2609,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryIterationCursor<'w, 's, D, F> {
// - fetch is only called once for each `archetype_entity`.
let item = unsafe {
D::fetch(
&query_state.fetch_state,
&mut self.fetch,
archetype_entity.id(),
archetype_entity.table_row(),

View File

@ -824,9 +824,9 @@ mod tests {
fn shrink_fetch<'wlong: 'wshort, 'wshort>(_: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {}
unsafe fn init_fetch<'w>(
unsafe fn init_fetch<'w, 's>(
_world: UnsafeWorldCell<'w>,
_state: &Self::State,
_state: &'s Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
@ -835,18 +835,18 @@ mod tests {
const IS_DENSE: bool = true;
#[inline]
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_state: &'s Self::State,
_archetype: &'w Archetype,
_table: &Table,
) {
}
#[inline]
unsafe fn set_table<'w>(
unsafe fn set_table<'w, 's>(
_fetch: &mut Self::Fetch<'w>,
_state: &Self::State,
_state: &'s Self::State,
_table: &'w Table,
) {
}
@ -882,16 +882,20 @@ mod tests {
unsafe impl QueryData for ReadsRData {
const IS_READ_ONLY: bool = true;
type ReadOnly = Self;
type Item<'w> = ();
type Item<'w, 's> = ();
fn shrink<'wlong: 'wshort, 'wshort>(_item: Self::Item<'wlong>) -> Self::Item<'wshort> {}
fn shrink<'wlong: 'wshort, 'wshort, 's>(
_item: Self::Item<'wlong, 's>,
) -> Self::Item<'wshort, 's> {
}
#[inline(always)]
unsafe fn fetch<'w>(
unsafe fn fetch<'w, 's>(
_state: &'s Self::State,
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
) -> Self::Item<'w, 's> {
}
}

View File

@ -39,7 +39,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> {
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
#[inline]
pub fn for_each<FN: Fn(QueryItem<'w, D>) + Send + Sync + Clone>(self, func: FN) {
pub fn for_each<FN: Fn(QueryItem<'w, 's, D>) + Send + Sync + Clone>(self, func: FN) {
self.for_each_init(|| {}, |_, item| func(item));
}
@ -76,7 +76,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> QueryParIter<'w, 's, D, F> {
#[inline]
pub fn for_each_init<FN, INIT, T>(self, init: INIT, func: FN)
where
FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone,
FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone,
INIT: Fn() -> T + Sync + Send + Clone,
{
let func = |mut init, item| {
@ -190,7 +190,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync>
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
#[inline]
pub fn for_each<FN: Fn(QueryItem<'w, D>) + Send + Sync + Clone>(self, func: FN) {
pub fn for_each<FN: Fn(QueryItem<'w, 's, D>) + Send + Sync + Clone>(self, func: FN) {
self.for_each_init(|| {}, |_, item| func(item));
}
@ -247,7 +247,7 @@ impl<'w, 's, D: ReadOnlyQueryData, F: QueryFilter, E: EntityEquivalent + Sync>
#[inline]
pub fn for_each_init<FN, INIT, T>(self, init: INIT, func: FN)
where
FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone,
FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone,
INIT: Fn() -> T + Sync + Send + Clone,
{
let func = |mut init, item| {
@ -345,7 +345,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync>
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
#[inline]
pub fn for_each<FN: Fn(QueryItem<'w, D>) + Send + Sync + Clone>(self, func: FN) {
pub fn for_each<FN: Fn(QueryItem<'w, 's, D>) + Send + Sync + Clone>(self, func: FN) {
self.for_each_init(|| {}, |_, item| func(item));
}
@ -402,7 +402,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter, E: EntityEquivalent + Sync>
#[inline]
pub fn for_each_init<FN, INIT, T>(self, init: INIT, func: FN)
where
FN: Fn(&mut T, QueryItem<'w, D>) + Send + Sync + Clone,
FN: Fn(&mut T, QueryItem<'w, 's, D>) + Send + Sync + Clone,
INIT: Fn() -> T + Sync + Send + Clone,
{
let func = |mut init, item| {

View File

@ -14,6 +14,7 @@ use crate::{
use crate::entity::UniqueEntityEquivalentSlice;
use alloc::vec::Vec;
use bevy_utils::prelude::DebugName;
use core::{fmt, ptr};
use fixedbitset::FixedBitSet;
use log::warn;
@ -672,7 +673,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
assert!(
component_access.is_subset(&self_access),
"Transmuted state for {} attempts to access terms that are not allowed by original state {}.",
core::any::type_name::<(NewD, NewF)>(), core::any::type_name::<(D, F)>()
DebugName::type_name::<(NewD, NewF)>(), DebugName::type_name::<(D, F)>()
);
QueryState {
@ -791,7 +792,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
assert!(
component_access.is_subset(&joined_component_access),
"Joined state for {} attempts to access terms that are not allowed by state {} joined with {}.",
core::any::type_name::<(NewD, NewF)>(), core::any::type_name::<(D, F)>(), core::any::type_name::<(OtherD, OtherF)>()
DebugName::type_name::<(NewD, NewF)>(), DebugName::type_name::<(D, F)>(), DebugName::type_name::<(OtherD, OtherF)>()
);
if self.archetype_generation != other.archetype_generation {
@ -845,13 +846,17 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// This can only be called for read-only queries, see [`Self::get_mut`] for write-queries.
///
/// If you need to get multiple items at once but get borrowing errors,
/// consider using [`Self::update_archetypes`] followed by multiple [`Self::get_manual`] calls,
/// or making a single call with [`Self::get_many`] or [`Self::iter_many`].
///
/// This is always guaranteed to run in `O(1)` time.
#[inline]
pub fn get<'w>(
&mut self,
world: &'w World,
entity: Entity,
) -> Result<ROQueryItem<'w, D>, QueryEntityError> {
) -> Result<ROQueryItem<'w, '_, D>, QueryEntityError> {
self.query(world).get_inner(entity)
}
@ -892,7 +897,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self,
world: &'w World,
entities: [Entity; N],
) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError> {
) -> Result<[ROQueryItem<'w, '_, D>; N], QueryEntityError> {
self.query(world).get_many_inner(entities)
}
@ -930,7 +935,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self,
world: &'w World,
entities: UniqueEntityArray<N>,
) -> Result<[ROQueryItem<'w, D>; N], QueryEntityError> {
) -> Result<[ROQueryItem<'w, '_, D>; N], QueryEntityError> {
self.query(world).get_many_unique_inner(entities)
}
@ -942,7 +947,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self,
world: &'w mut World,
entity: Entity,
) -> Result<D::Item<'w>, QueryEntityError> {
) -> Result<D::Item<'w, '_>, QueryEntityError> {
self.query_mut(world).get_inner(entity)
}
@ -989,7 +994,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self,
world: &'w mut World,
entities: [Entity; N],
) -> Result<[D::Item<'w>; N], QueryEntityError> {
) -> Result<[D::Item<'w, '_>; N], QueryEntityError> {
self.query_mut(world).get_many_mut_inner(entities)
}
@ -1034,7 +1039,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self,
world: &'w mut World,
entities: UniqueEntityArray<N>,
) -> Result<[D::Item<'w>; N], QueryEntityError> {
) -> Result<[D::Item<'w, '_>; N], QueryEntityError> {
self.query_mut(world).get_many_unique_inner(entities)
}
@ -1056,7 +1061,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&self,
world: &'w World,
entity: Entity,
) -> Result<ROQueryItem<'w, D>, QueryEntityError> {
) -> Result<ROQueryItem<'w, '_, D>, QueryEntityError> {
self.query_manual(world).get_inner(entity)
}
@ -1073,13 +1078,16 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
&mut self,
world: UnsafeWorldCell<'w>,
entity: Entity,
) -> Result<D::Item<'w>, QueryEntityError> {
) -> Result<D::Item<'w, '_>, QueryEntityError> {
self.query_unchecked(world).get_inner(entity)
}
/// Returns an [`Iterator`] over the query results for the given [`World`].
///
/// This can only be called for read-only queries, see [`Self::iter_mut`] for write-queries.
///
/// If you need to iterate multiple times at once but get borrowing errors,
/// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_manual`] calls.
#[inline]
pub fn iter<'w, 's>(&'s mut self, world: &'w World) -> QueryIter<'w, 's, D::ReadOnly, F> {
self.query(world).into_iter()
@ -1168,6 +1176,9 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// Items are returned in the order of the list of entities.
/// Entities that don't match the query are skipped.
///
/// If you need to iterate multiple times at once but get borrowing errors,
/// consider using [`Self::update_archetypes`] followed by multiple [`Self::iter_many_manual`] calls.
///
/// # See also
///
/// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items.
@ -1387,8 +1398,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
pub(crate) unsafe fn par_fold_init_unchecked_manual<'w, T, FN, INIT>(
&self,
pub(crate) unsafe fn par_fold_init_unchecked_manual<'w, 's, T, FN, INIT>(
&'s self,
init_accum: INIT,
world: UnsafeWorldCell<'w>,
batch_size: u32,
@ -1396,7 +1407,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
last_run: Tick,
this_run: Tick,
) where
FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone,
FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone,
INIT: Fn() -> T + Sync + Send + Clone,
{
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
@ -1501,8 +1512,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
pub(crate) unsafe fn par_many_unique_fold_init_unchecked_manual<'w, T, FN, INIT, E>(
&self,
pub(crate) unsafe fn par_many_unique_fold_init_unchecked_manual<'w, 's, T, FN, INIT, E>(
&'s self,
init_accum: INIT,
world: UnsafeWorldCell<'w>,
entity_list: &UniqueEntityEquivalentSlice<E>,
@ -1511,7 +1522,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
last_run: Tick,
this_run: Tick,
) where
FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone,
FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone,
INIT: Fn() -> T + Sync + Send + Clone,
E: EntityEquivalent + Sync,
{
@ -1564,8 +1575,8 @@ impl<D: ReadOnlyQueryData, F: QueryFilter> QueryState<D, F> {
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
pub(crate) unsafe fn par_many_fold_init_unchecked_manual<'w, T, FN, INIT, E>(
&self,
pub(crate) unsafe fn par_many_fold_init_unchecked_manual<'w, 's, T, FN, INIT, E>(
&'s self,
init_accum: INIT,
world: UnsafeWorldCell<'w>,
entity_list: &[E],
@ -1574,7 +1585,7 @@ impl<D: ReadOnlyQueryData, F: QueryFilter> QueryState<D, F> {
last_run: Tick,
this_run: Tick,
) where
FN: Fn(T, D::Item<'w>) -> T + Send + Sync + Clone,
FN: Fn(T, D::Item<'w, 's>) -> T + Send + Sync + Clone,
INIT: Fn() -> T + Sync + Send + Clone,
E: EntityEquivalent + Sync,
{
@ -1686,7 +1697,10 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
///
/// Simply unwrapping the [`Result`] also works, but should generally be reserved for tests.
#[inline]
pub fn single<'w>(&mut self, world: &'w World) -> Result<ROQueryItem<'w, D>, QuerySingleError> {
pub fn single<'w>(
&mut self,
world: &'w World,
) -> Result<ROQueryItem<'w, '_, D>, QuerySingleError> {
self.query(world).single_inner()
}
@ -1703,7 +1717,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
pub fn single_mut<'w>(
&mut self,
world: &'w mut World,
) -> Result<D::Item<'w>, QuerySingleError> {
) -> Result<D::Item<'w, '_>, QuerySingleError> {
self.query_mut(world).single_inner()
}
@ -1720,7 +1734,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
pub unsafe fn single_unchecked<'w>(
&mut self,
world: UnsafeWorldCell<'w>,
) -> Result<D::Item<'w>, QuerySingleError> {
) -> Result<D::Item<'w, '_>, QuerySingleError> {
self.query_unchecked(world).single_inner()
}
@ -1742,7 +1756,7 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
world: UnsafeWorldCell<'w>,
last_run: Tick,
this_run: Tick,
) -> Result<D::Item<'w>, QuerySingleError> {
) -> Result<D::Item<'w, '_>, QuerySingleError> {
// SAFETY:
// - The caller ensured we have the correct access to the world.
// - The caller ensured that the world matches.

View File

@ -42,7 +42,7 @@ use variadics_please::all_tuples;
/// [`QueryFilter`]: crate::query::QueryFilter
pub unsafe trait WorldQuery {
/// Per archetype/table state retrieved by this [`WorldQuery`] to compute [`Self::Item`](crate::query::QueryData::Item) for each entity.
type Fetch<'a>: Clone;
type Fetch<'w>: Clone;
/// State used to construct a [`Self::Fetch`](WorldQuery::Fetch). This will be cached inside [`QueryState`](crate::query::QueryState),
/// so it is best to move as much data / computation here as possible to reduce the cost of
@ -62,9 +62,9 @@ pub unsafe trait WorldQuery {
/// in to this function.
/// - `world` must have the **right** to access any access registered in `update_component_access`.
/// - There must not be simultaneous resource access conflicting with readonly resource access registered in [`WorldQuery::update_component_access`].
unsafe fn init_fetch<'w>(
unsafe fn init_fetch<'w, 's>(
world: UnsafeWorldCell<'w>,
state: &Self::State,
state: &'s Self::State,
last_run: Tick,
this_run: Tick,
) -> Self::Fetch<'w>;
@ -87,9 +87,9 @@ pub unsafe trait WorldQuery {
/// - `archetype` and `tables` must be from the same [`World`] that [`WorldQuery::init_state`] was called on.
/// - `table` must correspond to `archetype`.
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
state: &'s Self::State,
archetype: &'w Archetype,
table: &'w Table,
);
@ -101,7 +101,11 @@ pub unsafe trait WorldQuery {
///
/// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on.
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table);
unsafe fn set_table<'w, 's>(
fetch: &mut Self::Fetch<'w>,
state: &'s Self::State,
table: &'w Table,
);
/// Adds any component accesses used by this [`WorldQuery`] to `access`.
///
@ -166,7 +170,7 @@ macro_rules! impl_tuple_world_query {
}
#[inline]
unsafe fn init_fetch<'w>(world: UnsafeWorldCell<'w>, state: &Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> {
unsafe fn init_fetch<'w, 's>(world: UnsafeWorldCell<'w>, state: &'s Self::State, last_run: Tick, this_run: Tick) -> Self::Fetch<'w> {
let ($($name,)*) = state;
// SAFETY: The invariants are upheld by the caller.
($(unsafe { $name::init_fetch(world, $name, last_run, this_run) },)*)
@ -175,9 +179,9 @@ macro_rules! impl_tuple_world_query {
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
#[inline]
unsafe fn set_archetype<'w>(
unsafe fn set_archetype<'w, 's>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
state: &'s Self::State,
archetype: &'w Archetype,
table: &'w Table
) {
@ -188,7 +192,7 @@ macro_rules! impl_tuple_world_query {
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
unsafe fn set_table<'w, 's>(fetch: &mut Self::Fetch<'w>, state: &'s Self::State, table: &'w Table) {
let ($($name,)*) = fetch;
let ($($state,)*) = state;
// SAFETY: The invariants are upheld by the caller.

View File

@ -5,6 +5,7 @@
//!
//! Same as [`super::component`], but for bundles.
use alloc::boxed::Box;
use bevy_utils::prelude::DebugName;
use core::any::{Any, TypeId};
use crate::{
@ -172,7 +173,7 @@ impl<B: Bundle + Reflect + TypePath + BundleFromComponents> FromType<B> for Refl
_ => panic!(
"expected bundle `{}` to be named struct or tuple",
// FIXME: once we have unique reflect, use `TypePath`.
core::any::type_name::<B>(),
DebugName::type_name::<B>(),
),
}
}
@ -215,7 +216,7 @@ impl<B: Bundle + Reflect + TypePath + BundleFromComponents> FromType<B> for Refl
_ => panic!(
"expected bundle `{}` to be a named struct or tuple",
// FIXME: once we have unique reflect, use `TypePath`.
core::any::type_name::<B>(),
DebugName::type_name::<B>(),
),
}
}

View File

@ -70,7 +70,7 @@ use crate::{
},
};
use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry};
use disqualified::ShortName;
use bevy_utils::prelude::DebugName;
/// A struct used to operate on reflected [`Component`] trait of a type.
///
@ -308,7 +308,8 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
},
apply: |mut entity, reflected_component| {
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
let name = DebugName::type_name::<C>();
let name = name.shortname();
panic!("Cannot call `ReflectComponent::apply` on component {name}. It is immutable, and cannot modified through reflection");
}
@ -357,7 +358,8 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
reflect: |entity| entity.get::<C>().map(|c| c as &dyn Reflect),
reflect_mut: |entity| {
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
let name = DebugName::type_name::<C>();
let name = name.shortname();
panic!("Cannot call `ReflectComponent::reflect_mut` on component {name}. It is immutable, and cannot modified through reflection");
}
@ -370,7 +372,8 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
},
reflect_unchecked_mut: |entity| {
if !C::Mutability::MUTABLE {
let name = ShortName::of::<C>();
let name = DebugName::type_name::<C>();
let name = name.shortname();
panic!("Cannot call `ReflectComponent::reflect_unchecked_mut` on component {name}. It is immutable, and cannot modified through reflection");
}

View File

@ -18,6 +18,7 @@ mod from_world;
mod map_entities;
mod resource;
use bevy_utils::prelude::DebugName;
pub use bundle::{ReflectBundle, ReflectBundleFns};
pub use component::{ReflectComponent, ReflectComponentFns};
pub use entity_commands::ReflectCommandExt;
@ -148,7 +149,7 @@ pub fn from_reflect_with_fallback<T: Reflect + TypePath>(
`Default` or `FromWorld` traits. Are you perhaps missing a `#[reflect(Default)]` \
or `#[reflect(FromWorld)]`?",
// FIXME: once we have unique reflect, use `TypePath`.
core::any::type_name::<T>(),
DebugName::type_name::<T>(),
);
};

View File

@ -6,14 +6,16 @@ mod relationship_source_collection;
use alloc::format;
use bevy_utils::prelude::DebugName;
pub use related_methods::*;
pub use relationship_query::*;
pub use relationship_source_collection::*;
use crate::{
component::{Component, HookContext, Mutable},
component::{Component, Mutable},
entity::{ComponentCloneCtx, Entity, SourceComponent},
error::{ignore, CommandWithEntity, HandleError},
lifecycle::HookContext,
world::{DeferredWorld, EntityWorldMut},
};
use log::warn;
@ -104,8 +106,8 @@ pub trait Relationship: Component + Sized {
warn!(
"{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.",
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
core::any::type_name::<Self>(),
core::any::type_name::<Self>()
DebugName::type_name::<Self>(),
DebugName::type_name::<Self>()
);
world.commands().entity(entity).remove::<Self>();
return;
@ -124,8 +126,8 @@ pub trait Relationship: Component + Sized {
warn!(
"{}The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.",
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
core::any::type_name::<Self>(),
core::any::type_name::<Self>()
DebugName::type_name::<Self>(),
DebugName::type_name::<Self>()
);
world.commands().entity(entity).remove::<Self>();
}

View File

@ -1,6 +1,7 @@
use crate::{
bundle::Bundle,
entity::{hash_set::EntityHashSet, Entity},
prelude::Children,
relationship::{
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
},
@ -138,22 +139,26 @@ impl<'w> EntityWorldMut<'w> {
return self;
}
let Some(mut existing_relations) = self.get_mut::<R::RelationshipTarget>() else {
let Some(existing_relations) = self.get_mut::<R::RelationshipTarget>() else {
return self.add_related::<R>(related);
};
// We take the collection here so we can modify it without taking the component itself (this would create archetype move).
// We replace the component here with a dummy value so we can modify it without taking it (this would create archetype move).
// SAFETY: We eventually return the correctly initialized collection into the target.
let mut existing_relations = mem::replace(
existing_relations.collection_mut_risky(),
Collection::<R>::with_capacity(0),
let mut relations = mem::replace(
existing_relations.into_inner(),
<R as Relationship>::RelationshipTarget::from_collection_risky(
Collection::<R>::with_capacity(0),
),
);
let collection = relations.collection_mut_risky();
let mut potential_relations = EntityHashSet::from_iter(related.iter().copied());
let id = self.id();
self.world_scope(|world| {
for related in existing_relations.iter() {
for related in collection.iter() {
if !potential_relations.remove(related) {
world.entity_mut(related).remove::<R>();
}
@ -168,11 +173,9 @@ impl<'w> EntityWorldMut<'w> {
});
// SAFETY: The entities we're inserting will be the entities that were either already there or entities that we've just inserted.
existing_relations.clear();
existing_relations.extend_from_iter(related.iter().copied());
self.insert(R::RelationshipTarget::from_collection_risky(
existing_relations,
));
collection.clear();
collection.extend_from_iter(related.iter().copied());
self.insert(relations);
self
}
@ -238,11 +241,20 @@ impl<'w> EntityWorldMut<'w> {
assert_eq!(newly_related_entities, entities_to_relate, "`entities_to_relate` ({entities_to_relate:?}) didn't contain all entities that would end up related");
};
if !self.contains::<R::RelationshipTarget>() {
self.add_related::<R>(entities_to_relate);
match self.get_mut::<R::RelationshipTarget>() {
None => {
self.add_related::<R>(entities_to_relate);
return self;
};
return self;
}
Some(mut target) => {
// SAFETY: The invariants expected by this function mean we'll only be inserting entities that are already related.
let collection = target.collection_mut_risky();
collection.clear();
collection.extend_from_iter(entities_to_relate.iter().copied());
}
}
let this = self.id();
self.world_scope(|world| {
@ -251,32 +263,13 @@ impl<'w> EntityWorldMut<'w> {
}
for new_relation in newly_related_entities {
// We're changing the target collection manually so don't run the insert hook
// We changed the target collection manually so don't run the insert hook
world
.entity_mut(*new_relation)
.insert_with_relationship_hook_mode(R::from(this), RelationshipHookMode::Skip);
}
});
if !entities_to_relate.is_empty() {
if let Some(mut target) = self.get_mut::<R::RelationshipTarget>() {
// SAFETY: The invariants expected by this function mean we'll only be inserting entities that are already related.
let collection = target.collection_mut_risky();
collection.clear();
collection.extend_from_iter(entities_to_relate.iter().copied());
} else {
let mut empty =
<R::RelationshipTarget as RelationshipTarget>::Collection::with_capacity(
entities_to_relate.len(),
);
empty.extend_from_iter(entities_to_relate.iter().copied());
// SAFETY: We've just initialized this collection and we know there's no `RelationshipTarget` on `self`
self.insert(R::RelationshipTarget::from_collection_risky(empty));
}
}
self
}
@ -302,6 +295,15 @@ impl<'w> EntityWorldMut<'w> {
self
}
/// Despawns the children of this entity.
/// This entity will not be despawned.
///
/// This is a specialization of [`despawn_related`](EntityWorldMut::despawn_related), a more general method for despawning via relationships.
pub fn despawn_children(&mut self) -> &mut Self {
self.despawn_related::<Children>();
self
}
/// Inserts a component or bundle of components into the entity and all related entities,
/// traversing the relationship tracked in `S` in a breadth-first manner.
///
@ -467,6 +469,14 @@ impl<'a> EntityCommands<'a> {
})
}
/// Despawns the children of this entity.
/// This entity will not be despawned.
///
/// This is a specialization of [`despawn_related`](EntityCommands::despawn_related), a more general method for despawning via relationships.
pub fn despawn_children(&mut self) -> &mut Self {
self.despawn_related::<Children>()
}
/// Inserts a component or bundle of components into the entity and all related entities,
/// traversing the relationship tracked in `S` in a breadth-first manner.
///
@ -650,4 +660,61 @@ mod tests {
assert_eq!(world.entity(b).get::<ChildOf>(), None);
assert_eq!(world.entity(c).get::<ChildOf>(), None);
}
#[test]
fn replace_related_works() {
let mut world = World::new();
let child1 = world.spawn_empty().id();
let child2 = world.spawn_empty().id();
let child3 = world.spawn_empty().id();
let mut parent = world.spawn_empty();
parent.add_children(&[child1, child2]);
let child_value = ChildOf(parent.id());
let some_child = Some(&child_value);
parent.replace_children(&[child2, child3]);
let children = parent.get::<Children>().unwrap().collection();
assert_eq!(children, &[child2, child3]);
assert_eq!(parent.world().get::<ChildOf>(child1), None);
assert_eq!(parent.world().get::<ChildOf>(child2), some_child);
assert_eq!(parent.world().get::<ChildOf>(child3), some_child);
parent.replace_children_with_difference(&[child3], &[child1, child2], &[child1]);
let children = parent.get::<Children>().unwrap().collection();
assert_eq!(children, &[child1, child2]);
assert_eq!(parent.world().get::<ChildOf>(child1), some_child);
assert_eq!(parent.world().get::<ChildOf>(child2), some_child);
assert_eq!(parent.world().get::<ChildOf>(child3), None);
}
#[test]
fn replace_related_keeps_data() {
#[derive(Component)]
#[relationship(relationship_target = Parent)]
pub struct Child(Entity);
#[derive(Component)]
#[relationship_target(relationship = Child)]
pub struct Parent {
#[relationship]
children: Vec<Entity>,
pub data: u8,
}
let mut world = World::new();
let child1 = world.spawn_empty().id();
let child2 = world.spawn_empty().id();
let mut parent = world.spawn_empty();
parent.add_related::<Child>(&[child1]);
parent.get_mut::<Parent>().unwrap().data = 42;
parent.replace_related_with_difference::<Child>(&[child1], &[child2], &[child2]);
let data = parent.get::<Parent>().unwrap().data;
assert_eq!(data, 42);
parent.replace_related::<Child>(&[child1]);
let data = parent.get::<Parent>().unwrap().data;
assert_eq!(data, 42);
}
}

View File

@ -14,7 +14,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// target entity of that relationship.
pub fn related<R: Relationship>(&'w self, entity: Entity) -> Option<Entity>
where
<D as QueryData>::ReadOnly: QueryData<Item<'w> = &'w R>,
<D as QueryData>::ReadOnly: QueryData<Item<'w, 's> = &'w R>,
{
self.get(entity).map(R::get).ok()
}
@ -26,7 +26,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
entity: Entity,
) -> impl Iterator<Item = Entity> + 'w
where
<D as QueryData>::ReadOnly: QueryData<Item<'w> = &'w S>,
<D as QueryData>::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
{
self.get(entity)
.into_iter()
@ -42,7 +42,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// If your relationship is not a tree (like Bevy's hierarchy), be sure to stop if you encounter a duplicate entity.
pub fn root_ancestor<R: Relationship>(&'w self, entity: Entity) -> Entity
where
<D as QueryData>::ReadOnly: QueryData<Item<'w> = &'w R>,
<D as QueryData>::ReadOnly: QueryData<Item<'w, 's> = &'w R>,
{
// Recursively search up the tree until we're out of parents
match self.get(entity) {
@ -60,9 +60,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
pub fn iter_leaves<S: RelationshipTarget>(
&'w self,
entity: Entity,
) -> impl Iterator<Item = Entity> + 'w
) -> impl Iterator<Item = Entity> + use<'w, 's, S, D, F>
where
<D as QueryData>::ReadOnly: QueryData<Item<'w> = &'w S>,
<D as QueryData>::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
self.iter_descendants_depth_first(entity).filter(|entity| {
@ -80,7 +80,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
entity: Entity,
) -> impl Iterator<Item = Entity> + 'w
where
D::ReadOnly: QueryData<Item<'w> = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>,
D::ReadOnly: QueryData<Item<'w, 's> = (Option<&'w R>, Option<&'w R::RelationshipTarget>)>,
{
self.get(entity)
.ok()
@ -103,7 +103,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
entity: Entity,
) -> DescendantIter<'w, 's, D, F, S>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
{
DescendantIter::new(self, entity)
}
@ -120,7 +120,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
entity: Entity,
) -> DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
DescendantDepthFirstIter::new(self, entity)
@ -137,7 +137,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
entity: Entity,
) -> AncestorIter<'w, 's, D, F, R>
where
D::ReadOnly: QueryData<Item<'w> = &'w R>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w R>,
{
AncestorIter::new(self, entity)
}
@ -148,7 +148,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Traverses the hierarchy breadth-first.
pub struct DescendantIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
{
children_query: &'w Query<'w, 's, D, F>,
vecdeque: VecDeque<Entity>,
@ -156,7 +156,7 @@ where
impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> DescendantIter<'w, 's, D, F, S>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
{
/// Returns a new [`DescendantIter`].
pub fn new(children_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self {
@ -174,7 +174,7 @@ where
impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator
for DescendantIter<'w, 's, D, F, S>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
{
type Item = Entity;
@ -194,7 +194,7 @@ where
/// Traverses the hierarchy depth-first.
pub struct DescendantDepthFirstIter<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
{
children_query: &'w Query<'w, 's, D, F>,
stack: SmallVec<[Entity; 8]>,
@ -203,7 +203,7 @@ where
impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget>
DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
/// Returns a new [`DescendantDepthFirstIter`].
@ -220,7 +220,7 @@ where
impl<'w, 's, D: QueryData, F: QueryFilter, S: RelationshipTarget> Iterator
for DescendantDepthFirstIter<'w, 's, D, F, S>
where
D::ReadOnly: QueryData<Item<'w> = &'w S>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w S>,
SourceIter<'w, S>: DoubleEndedIterator,
{
type Item = Entity;
@ -239,7 +239,7 @@ where
/// An [`Iterator`] of [`Entity`]s over the ancestors of an [`Entity`].
pub struct AncestorIter<'w, 's, D: QueryData, F: QueryFilter, R: Relationship>
where
D::ReadOnly: QueryData<Item<'w> = &'w R>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w R>,
{
parent_query: &'w Query<'w, 's, D, F>,
next: Option<Entity>,
@ -247,7 +247,7 @@ where
impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> AncestorIter<'w, 's, D, F, R>
where
D::ReadOnly: QueryData<Item<'w> = &'w R>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w R>,
{
/// Returns a new [`AncestorIter`].
pub fn new(parent_query: &'w Query<'w, 's, D, F>, entity: Entity) -> Self {
@ -261,7 +261,7 @@ where
impl<'w, 's, D: QueryData, F: QueryFilter, R: Relationship> Iterator
for AncestorIter<'w, 's, D, F, R>
where
D::ReadOnly: QueryData<Item<'w> = &'w R>,
D::ReadOnly: QueryData<Item<'w, 's> = &'w R>,
{
type Item = Entity;

View File

@ -86,13 +86,13 @@ pub trait OrderedRelationshipSourceCollection: RelationshipSourceCollection {
/// Inserts the entity at a specific index.
/// If the index is too large, the entity will be added to the end of the collection.
fn insert(&mut self, index: usize, entity: Entity);
/// Removes the entity at the specified idnex if it exists.
/// Removes the entity at the specified index if it exists.
fn remove_at(&mut self, index: usize) -> Option<Entity>;
/// Inserts the entity at a specific index.
/// This will never reorder other entities.
/// If the index is too large, the entity will be added to the end of the collection.
fn insert_stable(&mut self, index: usize, entity: Entity);
/// Removes the entity at the specified idnex if it exists.
/// Removes the entity at the specified index if it exists.
/// This will never reorder other entities.
fn remove_at_stable(&mut self, index: usize) -> Option<Entity>;
/// Sorts the source collection.

View File

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

View File

@ -102,7 +102,7 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
let mut system_has_conditions_cache = HashMap::<usize, bool>::default();
let mut is_valid_explicit_sync_point = |system: NodeId| {
let index = system.index();
is_apply_deferred(graph.systems[index].get().unwrap())
is_apply_deferred(&graph.systems[index].get().unwrap().system)
&& !*system_has_conditions_cache
.entry(index)
.or_insert_with(|| system_has_conditions(graph, system))
@ -138,7 +138,11 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
} else if !node_needs_sync {
// No previous node has postponed sync points to add so check if the system itself
// has deferred params that require a sync point to apply them.
node_needs_sync = graph.systems[node.index()].get().unwrap().has_deferred();
node_needs_sync = graph.systems[node.index()]
.get()
.unwrap()
.system
.has_deferred();
}
for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) {
@ -148,7 +152,11 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
let mut edge_needs_sync = node_needs_sync;
if node_needs_sync
&& !graph.systems[target.index()].get().unwrap().is_exclusive()
&& !graph.systems[target.index()]
.get()
.unwrap()
.system
.is_exclusive()
&& self.no_sync_edges.contains(&(*node, target))
{
// The node has deferred params to apply, but this edge is ignoring sync points.
@ -190,7 +198,7 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
continue;
}
if is_apply_deferred(graph.systems[target.index()].get().unwrap()) {
if is_apply_deferred(&graph.systems[target.index()].get().unwrap().system) {
// We don't need to insert a sync point since ApplyDeferred is a sync point
// already!
continue;

View File

@ -1,4 +1,5 @@
use alloc::{borrow::Cow, boxed::Box, format};
use alloc::{boxed::Box, format};
use bevy_utils::prelude::DebugName;
use core::ops::Not;
use crate::system::{
@ -11,8 +12,18 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// A system that determines if one or more scheduled systems should run.
///
/// Implemented for functions and closures that convert into [`System<Out=bool>`](System)
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
/// `SystemCondition` is sealed and implemented for functions and closures with
/// [read-only](crate::system::ReadOnlySystemParam) parameters that convert into
/// [`System<Out = bool>`](System), [`System<Out = Result<(), BevyError>>`](System) or
/// [`System<Out = Result<bool, BevyError>>`](System).
///
/// `SystemCondition` offers a private method
/// (called by [`run_if`](crate::schedule::IntoScheduleConfigs::run_if) and the provided methods)
/// that converts the implementing system into a condition (system) returning a bool.
/// Depending on the output type of the implementing system:
/// - `bool`: the implementing system is used as the condition;
/// - `Result<(), BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(())`;
/// - `Result<bool, BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(true)`.
///
/// # Marker type parameter
///
@ -31,7 +42,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// ```
///
/// # Examples
/// A condition that returns true every other time it's called.
/// A condition that returns `true` every other time it's called.
/// ```
/// # use bevy_ecs::prelude::*;
/// fn every_other_time() -> impl SystemCondition<()> {
@ -54,7 +65,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// # assert!(!world.resource::<DidRun>().0);
/// ```
///
/// A condition that takes a bool as an input and returns it unchanged.
/// A condition that takes a `bool` as an input and returns it unchanged.
///
/// ```
/// # use bevy_ecs::prelude::*;
@ -71,8 +82,30 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// # world.insert_resource(DidRun(false));
/// # app.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait SystemCondition<Marker, In: SystemInput = ()>:
sealed::SystemCondition<Marker, In>
/// ```
///
/// A condition returning a `Result<(), BevyError>`
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component)] struct Player;
/// fn player_exists(q_player: Query<(), With<Player>>) -> Result {
/// Ok(q_player.single()?)
/// }
///
/// # let mut app = Schedule::default();
/// # #[derive(Resource)] struct DidRun(bool);
/// # fn my_system(mut did_run: ResMut<DidRun>) { did_run.0 = true; }
/// app.add_systems(my_system.run_if(player_exists));
/// # let mut world = World::new();
/// # world.insert_resource(DidRun(false));
/// # app.run(&mut world);
/// # assert!(!world.resource::<DidRun>().0);
/// # world.spawn(Player);
/// # app.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait SystemCondition<Marker, In: SystemInput = (), Out = bool>:
sealed::SystemCondition<Marker, In, Out>
{
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `and` return `true`.
@ -122,7 +155,7 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(and);
let name = format!("{} && {}", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
CombinatorSystem::new(a, b, DebugName::owned(name))
}
/// Returns a new run condition that only returns `false`
@ -174,7 +207,7 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(nand);
let name = format!("!({} && {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
CombinatorSystem::new(a, b, DebugName::owned(name))
}
/// Returns a new run condition that only returns `true`
@ -226,7 +259,7 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(nor);
let name = format!("!({} || {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
CombinatorSystem::new(a, b, DebugName::owned(name))
}
/// Returns a new run condition that returns `true`
@ -273,7 +306,7 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(or);
let name = format!("{} || {}", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
CombinatorSystem::new(a, b, DebugName::owned(name))
}
/// Returns a new run condition that only returns `true`
@ -325,7 +358,7 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(xnor);
let name = format!("!({} ^ {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
CombinatorSystem::new(a, b, DebugName::owned(name))
}
/// Returns a new run condition that only returns `true`
@ -367,32 +400,65 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(xor);
let name = format!("({} ^ {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
CombinatorSystem::new(a, b, DebugName::owned(name))
}
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F where
F: sealed::SystemCondition<Marker, In>
impl<Marker, In: SystemInput, Out, F> SystemCondition<Marker, In, Out> for F where
F: sealed::SystemCondition<Marker, In, Out>
{
}
mod sealed {
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
use crate::{
error::BevyError,
system::{IntoSystem, ReadOnlySystem, SystemInput},
};
pub trait SystemCondition<Marker, In: SystemInput>:
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
pub trait SystemCondition<Marker, In: SystemInput, Out>:
IntoSystem<In, Out, Marker, System = Self::ReadOnlySystem>
{
// This associated type is necessary to let the compiler
// know that `Self::System` is `ReadOnlySystem`.
type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
type ReadOnlySystem: ReadOnlySystem<In = In, Out = Out>;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool>;
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, bool> for F
where
F: IntoSystem<In, bool, Marker>,
F::System: ReadOnlySystem,
{
type ReadOnlySystem = F::System;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
IntoSystem::into_system(self)
}
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<(), BevyError>> for F
where
F: IntoSystem<In, Result<(), BevyError>, Marker>,
F::System: ReadOnlySystem,
{
type ReadOnlySystem = F::System;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
IntoSystem::into_system(self.map(|result| result.is_ok()))
}
}
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<bool, BevyError>> for F
where
F: IntoSystem<In, Result<bool, BevyError>, Marker>,
F::System: ReadOnlySystem,
{
type ReadOnlySystem = F::System;
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
IntoSystem::into_system(self.map(|result| matches!(result, Ok(true))))
}
}
}
@ -401,10 +467,10 @@ pub mod common_conditions {
use super::{NotSystem, SystemCondition};
use crate::{
change_detection::DetectChanges,
event::{Event, EventReader},
event::{BufferedEvent, EventReader},
lifecycle::RemovedComponents,
prelude::{Component, Query, With},
query::QueryFilter,
removal_detection::RemovedComponents,
resource::Resource,
system::{In, IntoSystem, Local, Res, System, SystemInput},
};
@ -863,7 +929,7 @@ pub mod common_conditions {
/// my_system.run_if(on_event::<MyEvent>),
/// );
///
/// #[derive(Event)]
/// #[derive(Event, BufferedEvent)]
/// struct MyEvent;
///
/// fn my_system(mut counter: ResMut<Counter>) {
@ -880,7 +946,7 @@ pub mod common_conditions {
/// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 1);
/// ```
pub fn on_event<T: Event>(mut reader: EventReader<T>) -> bool {
pub fn on_event<T: BufferedEvent>(mut reader: EventReader<T>) -> bool {
// The events need to be consumed, so that there are no false positives on subsequent
// calls of the run condition. Simply checking `is_empty` would not be enough.
// PERF: note that `count` is efficient (not actually looping/iterating),
@ -1263,6 +1329,7 @@ where
#[cfg(test)]
mod tests {
use super::{common_conditions::*, SystemCondition};
use crate::event::{BufferedEvent, Event};
use crate::query::With;
use crate::{
change_detection::ResMut,
@ -1271,7 +1338,7 @@ mod tests {
system::Local,
world::World,
};
use bevy_ecs_macros::{Event, Resource};
use bevy_ecs_macros::Resource;
#[derive(Resource, Default)]
struct Counter(usize);
@ -1382,7 +1449,7 @@ mod tests {
#[derive(Component)]
struct TestComponent;
#[derive(Event)]
#[derive(Event, BufferedEvent)]
struct TestEvent;
#[derive(Resource)]

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