Merge branch 'main' into reflect-auto-registration
This commit is contained in:
commit
e383ab55e9
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -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: |
|
||||
|
||||
73
Cargo.toml
73
Cargo.toml
@ -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
|
||||
|
||||
113
assets/branding/bevy_solari.svg
Normal file
113
assets/branding/bevy_solari.svg
Normal 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 |
@ -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>>);
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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},
|
||||
};
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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"] }
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
@ -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> },
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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 = [
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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)) = (
|
||||
|
||||
@ -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) =
|
||||
|
||||
@ -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)) = (
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>();
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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> {
|
||||
|
||||
@ -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,
|
||||
|
||||
38
crates/bevy_core_widgets/Cargo.toml
Normal file
38
crates/bevy_core_widgets/Cargo.toml
Normal 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
|
||||
141
crates/bevy_core_widgets/src/core_button.rs
Normal file
141
crates/bevy_core_widgets/src/core_button.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use accesskit::Role;
|
||||
use bevy_a11y::AccessibilityNode;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::query::Has;
|
||||
use bevy_ecs::system::ResMut;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
observer::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);
|
||||
}
|
||||
}
|
||||
179
crates/bevy_core_widgets/src/core_checkbox.rs
Normal file
179
crates/bevy_core_widgets/src/core_checkbox.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
514
crates/bevy_core_widgets/src/core_slider.rs
Normal file
514
crates/bevy_core_widgets/src/core_slider.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
38
crates/bevy_core_widgets/src/lib.rs
Normal file
38
crates/bevy_core_widgets/src/lib.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
@ -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]
|
||||
|
||||
@ -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)]
|
||||
|
||||
@ -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,
|
||||
)>,
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
});
|
||||
|
||||
@ -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) {}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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);)*
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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>(),
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>>()
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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>) {
|
||||
|
||||
@ -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>) {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`.
|
||||
///
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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)]
|
||||
|
||||
643
crates/bevy_ecs/src/lifecycle.rs
Normal file
643
crates/bevy_ecs/src/lifecycle.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -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
@ -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);
|
||||
}
|
||||
|
||||
@ -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<_>>()
|
||||
|
||||
@ -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
@ -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,
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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| {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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>(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
|
||||
|
||||
@ -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>(),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -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>();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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
Loading…
Reference in New Issue
Block a user