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

This commit is contained in:
Eagster 2025-06-23 11:04:45 -04:00 committed by GitHub
commit b543a4ac54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
181 changed files with 4544 additions and 2243 deletions

View File

@ -49,7 +49,8 @@ jobs:
--exclude ci \
--exclude errors \
--exclude bevy_mobile_example \
--exclude build-wasm-example
--exclude build-wasm-example \
--exclude no_std_library
- name: Create PR
uses: peter-evans/create-pull-request@v7

View File

@ -1,6 +1,6 @@
[package]
name = "bevy"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
categories = ["game-engines", "graphics", "gui", "rendering"]
description = "A refreshingly simple data-driven game engine and app framework"
@ -72,7 +72,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"
@ -559,12 +558,12 @@ hotpatching = ["bevy_internal/hotpatching"]
debug = ["bevy_internal/debug"]
[dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
bevy_internal = { path = "crates/bevy_internal", version = "0.17.0-dev", default-features = false }
tracing = { version = "0.1", default-features = false, optional = true }
# Wasm does not support dynamic linking.
[target.'cfg(not(target_family = "wasm"))'.dependencies]
bevy_dylib = { path = "crates/bevy_dylib", version = "0.16.0-dev", default-features = false, optional = true }
bevy_dylib = { path = "crates/bevy_dylib", version = "0.17.0-dev", default-features = false, optional = true }
[dev-dependencies]
rand = "0.8.0"
@ -574,14 +573,14 @@ flate2 = "1.0"
serde = { version = "1", features = ["derive"] }
serde_json = "1.0.140"
bytemuck = "1.7"
bevy_render = { path = "crates/bevy_render", version = "0.16.0-dev", default-features = false }
bevy_render = { path = "crates/bevy_render", version = "0.17.0-dev", default-features = false }
# The following explicit dependencies are needed for proc macros to work inside of examples as they are part of the bevy crate itself.
bevy_ecs = { path = "crates/bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_state = { path = "crates/bevy_state", version = "0.16.0-dev", default-features = false }
bevy_asset = { path = "crates/bevy_asset", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "crates/bevy_reflect", version = "0.16.0-dev", default-features = false }
bevy_image = { path = "crates/bevy_image", version = "0.16.0-dev", default-features = false }
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "crates/bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_state = { path = "crates/bevy_state", version = "0.17.0-dev", default-features = false }
bevy_asset = { path = "crates/bevy_asset", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "crates/bevy_reflect", version = "0.17.0-dev", default-features = false }
bevy_image = { path = "crates/bevy_image", version = "0.17.0-dev", default-features = false }
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.17.0-dev", default-features = false }
# Needed to poll Task examples
futures-lite = "2.0.1"
async-std = "1.13"
@ -608,7 +607,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]
@ -1285,9 +1284,9 @@ required-features = ["bevy_solari"]
[package.metadata.example.solari]
name = "Solari"
description = "Demonstrates realtime dynamic global illumination rendering using Bevy Solari."
description = "Demonstrates realtime dynamic raytraced lighting using Bevy Solari."
category = "3D Rendering"
wasm = false
wasm = false # Raytracing is not supported on the web
[[example]]
name = "spherical_area_lights"

View File

@ -49,6 +49,7 @@ impl BenchModify for Table {
black_box(self.0)
}
}
impl BenchModify for Sparse {
fn bench_modify(&mut self) -> f32 {
self.0 += 1f32;

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_a11y"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides accessibility support for Bevy Engine"
homepage = "https://bevy.org"
@ -40,10 +40,10 @@ critical-section = [
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
# other
accesskit = { version = "0.19", default-features = false }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_animation"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides animation functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -10,22 +10,22 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", 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_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
"petgraph",
] }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
"serialize",
] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_anti_aliasing"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides various anti aliasing implementations for Bevy Engine"
homepage = "https://bevy.org"
@ -16,17 +16,17 @@ smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
[dependencies]
# bevy
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
# other
tracing = { version = "0.1", default-features = false, features = ["std"] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_app"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides core App functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -79,12 +79,12 @@ hotpatching = [
[dependencies]
# bevy
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false }
# other
downcast-rs = { version = "2", default-features = false }

View File

@ -1582,7 +1582,7 @@ mod tests {
app.add_systems(EnterMainMenu, (foo, bar));
app.world_mut().run_schedule(EnterMainMenu);
assert_eq!(app.world().entities().count_constructed(), 2);
assert_eq!(app.world().entity_count(), 2);
}
#[test]

View File

@ -88,6 +88,7 @@ impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
}
impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self._p.hash(state);

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_asset"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides asset functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -19,19 +19,19 @@ watch = []
trace = []
[dependencies]
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_asset_macros = { path = "macros", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
bevy_asset_macros = { path = "macros", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
"uuid",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [
"async_executor",
] }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
] }
@ -65,7 +65,7 @@ uuid = { version = "1.13.1", default-features = false, features = [
tracing = { version = "0.1", default-features = false }
[target.'cfg(target_os = "android")'.dependencies]
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
[target.'cfg(target_arch = "wasm32")'.dependencies]
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
@ -78,13 +78,13 @@ web-sys = { version = "0.3", features = [
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
"web",
] }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [
"web",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
"web",
] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_asset_macros"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Derive implementations for bevy_asset"
homepage = "https://bevy.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
syn = "2.0"
proc-macro2 = "1.0"

View File

@ -20,6 +20,7 @@ pub trait DirectAssetAccessExt {
settings: impl Fn(&mut S) + Send + Sync + 'static,
) -> Handle<A>;
}
impl DirectAssetAccessExt for World {
/// Insert an asset similarly to [`Assets::add`].
///

View File

@ -56,6 +56,7 @@ pub(crate) struct EmbeddedEventHandler {
dir: Dir,
last_event: Option<AssetSourceEvent>,
}
impl FilesystemEventHandler for EmbeddedEventHandler {
fn begin(&mut self) {
self.last_event = None;

View File

@ -141,16 +141,19 @@ impl EmbeddedAssetRegistry {
pub trait GetAssetServer {
fn get_asset_server(&self) -> &AssetServer;
}
impl GetAssetServer for App {
fn get_asset_server(&self) -> &AssetServer {
self.world().get_asset_server()
}
}
impl GetAssetServer for World {
fn get_asset_server(&self) -> &AssetServer {
self.resource()
}
}
impl GetAssetServer for AssetServer {
fn get_asset_server(&self) -> &AssetServer {
self

View File

@ -223,6 +223,7 @@ pub struct ReflectHandle {
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
}
impl ReflectHandle {
/// The [`TypeId`] of the asset
pub fn asset_type_id(&self) -> TypeId {

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_audio"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides audio functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -10,13 +10,13 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
# other
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
@ -36,10 +36,10 @@ coreaudio-sys = { version = "0.2.17", default-features = false }
rodio = { version = "0.20", default-features = false, features = [
"wasm-bindgen",
] }
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
"web",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
"web",
] }

View File

@ -57,6 +57,7 @@ pub struct PlaybackRemoveMarker;
pub(crate) struct EarPositions<'w, 's> {
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
}
impl<'w, 's> EarPositions<'w, 's> {
/// Gets a set of transformed ear positions.
///

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_color"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Types for representing and manipulating color values"
homepage = "https://bevy.org"
@ -10,10 +10,10 @@ keywords = ["bevy", "color"]
rust-version = "1.85.0"
[dependencies]
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false, features = [
"curve",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
bytemuck = { version = "1", features = ["derive"] }
serde = { version = "1.0", features = [
"derive",

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_core_pipeline"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
authors = [
"Bevy Contributors <bevyengine@gmail.com>",
@ -20,20 +20,20 @@ tonemapping_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", 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_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
"serialize",
] }

View File

@ -80,6 +80,7 @@ impl From<TextureUsages> for Camera3dDepthTextureUsage {
Self(value.bits())
}
}
impl From<Camera3dDepthTextureUsage> for TextureUsages {
fn from(value: Camera3dDepthTextureUsage) -> Self {
Self::from_bits_truncate(value.0)

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_core_widgets"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Unstyled common widgets for Bevy Engine"
homepage = "https://bevyengine.org"
@ -10,17 +10,17 @@ 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_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_a11y = { path = "../bevy_a11y", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
"bevy_ui_picking_backend",
] }

View File

@ -2,7 +2,6 @@ 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,
@ -11,7 +10,8 @@ use bevy_ecs::{
system::{Commands, Query, SystemId},
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_input::ButtonState;
use bevy_input_focus::FocusedInput;
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
use bevy_ui::{InteractionDisabled, Pressed};
@ -36,6 +36,7 @@ fn button_on_key_event(
if !disabled {
let event = &trigger.event().input;
if !event.repeat
&& event.state == ButtonState::Pressed
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
{
if let Some(on_click) = bstate.on_click {
@ -65,24 +66,12 @@ fn button_on_pointer_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;
}
if !disabled && !pressed {
commands.entity(button).insert(Pressed);
}
}
}

View File

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

View File

@ -3,12 +3,11 @@ 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::hierarchy::Children;
use bevy_ecs::lifecycle::Insert;
use bevy_ecs::query::Has;
use bevy_ecs::system::{In, Res, ResMut};
use bevy_ecs::system::{In, Res};
use bevy_ecs::world::DeferredWorld;
use bevy_ecs::{
component::Component,
@ -18,7 +17,7 @@ use bevy_ecs::{
};
use bevy_input::keyboard::{KeyCode, KeyboardInput};
use bevy_input::ButtonState;
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
use bevy_input_focus::FocusedInput;
use bevy_log::warn_once;
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};
@ -207,43 +206,18 @@ pub(crate) fn slider_on_pointer_down(
)>,
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;
}

View File

@ -15,11 +15,13 @@
// 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,
@ -31,6 +33,6 @@ pub struct CoreWidgetsPlugin;
impl Plugin for CoreWidgetsPlugin {
fn build(&self, app: &mut App) {
app.add_plugins((CoreButtonPlugin, CoreSliderPlugin));
app.add_plugins((CoreButtonPlugin, CoreCheckboxPlugin, CoreSliderPlugin));
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_derive"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides derive implementations for Bevy Engine"
homepage = "https://bevy.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.16.0-dev" }
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.17.0-dev" }
quote = "1.0"
syn = { version = "2.0", features = ["full"] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_dev_tools"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Collection of developer tools for the Bevy Engine"
homepage = "https://bevy.org"
@ -13,21 +13,21 @@ bevy_ci_testing = ["serde", "ron"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", 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_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.16.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
bevy_state = { path = "../bevy_state", version = "0.16.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_state = { path = "../bevy_state", version = "0.17.0-dev" }
# other
serde = { version = "1.0", features = ["derive"], optional = true }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_diagnostic"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides diagnostic functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -53,12 +53,12 @@ critical-section = [
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"alloc",
] }

View File

@ -17,11 +17,13 @@ pub struct FrameTimeDiagnosticsPlugin {
/// The smoothing factor for the exponential moving average. Usually `2.0 / (history_length + 1.0)`.
pub smoothing_factor: f64,
}
impl Default for FrameTimeDiagnosticsPlugin {
fn default() -> Self {
Self::new(DEFAULT_MAX_HISTORY_LENGTH)
}
}
impl FrameTimeDiagnosticsPlugin {
/// Creates a new `FrameTimeDiagnosticsPlugin` with the specified `max_history_length` and a
/// reasonable `smoothing_factor`.

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_dylib"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Force the Bevy Engine to be dynamically linked for faster linking"
homepage = "https://bevy.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
crate-type = ["dylib"]
[dependencies]
bevy_internal = { path = "../bevy_internal", version = "0.16.0-dev", default-features = false }
bevy_internal = { path = "../bevy_internal", version = "0.17.0-dev", default-features = false }
[lints]
workspace = true

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ecs"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Bevy Engine's entity component system"
homepage = "https://bevy.org"
@ -86,14 +86,14 @@ critical-section = [
hotpatching = ["dep:subsecond"]
[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
bevy_ptr = { path = "../bevy_ptr", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
"smallvec",
], default-features = false, optional = true }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_ecs_macros = { path = "macros", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_ecs_macros = { path = "macros", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"alloc",
] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_ecs_macros"
version = "0.16.0-dev"
version = "0.17.0-dev"
description = "Bevy ECS Macros"
edition = "2024"
license = "MIT OR Apache-2.0"
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
syn = { version = "2.0.99", features = ["full", "extra-traits"] }
quote = "1.0"

View File

@ -792,6 +792,11 @@ fn derive_relationship(
#relationship_member: entity
}
}
#[inline]
fn set_risky(&mut self, entity: Entity) {
self.#relationship_member = entity;
}
}
}))
}

View File

@ -16,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,
@ -79,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()
@ -86,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!(
@ -98,7 +100,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
}
}
field_kind.push(BundleFieldKind::Component);
field_kind.push(kind);
}
let field = named_fields
@ -111,82 +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;
let from_components = attributes.impl_from_components.then(|| quote! {
// SAFETY:
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
#[allow(deprecated)]
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
{
Self{
#(#field_from_components)*
}
}
}
});
let attribute_errors = &errors;
TokenStream::from(quote! {
#(#attribute_errors)*
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
@ -196,27 +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)*
) {
#(<#active_field_types as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);)*
}
}
};
#from_components
let dynamic_bundle_impl = quote! {
#[allow(deprecated)]
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
type Effect = ();
@ -226,9 +179,36 @@ 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
})
}

View File

@ -83,7 +83,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
let user_generics_with_world_and_state = {
let mut generics = ast.generics;
generics.params.insert(0, parse_quote!('__w));
generics.params.insert(0, parse_quote!('__s));
generics.params.insert(1, parse_quote!('__s));
generics
};
let (

View File

@ -960,6 +960,7 @@ impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {
&self.archetypes[index.start.0.index()..]
}
}
impl Index<ArchetypeId> for Archetypes {
type Output = Archetype;

View File

@ -2410,4 +2410,13 @@ mod tests {
assert_eq!(world.resource::<Count>().0, 3);
}
#[derive(Bundle)]
#[expect(unused, reason = "tests the output of the derive macro is valid")]
struct Ignore {
#[bundle(ignore)]
foo: i32,
#[bundle(ignore)]
bar: i32,
}
}

View File

@ -623,6 +623,7 @@ pub trait ComponentMutability: private::Seal + 'static {
pub struct Immutable;
impl private::Seal for Immutable {}
impl ComponentMutability for Immutable {
const MUTABLE: bool = false;
}
@ -633,6 +634,7 @@ impl ComponentMutability for Immutable {
pub struct Mutable;
impl private::Seal for Mutable {}
impl ComponentMutability for Mutable {
const MUTABLE: bool = true;
}
@ -2968,6 +2970,7 @@ impl<T> Default for DefaultCloneBehaviorSpecialization<T> {
pub trait DefaultCloneBehaviorBase {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Default
@ -2979,6 +2982,7 @@ impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
pub trait DefaultCloneBehaviorViaClone {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C: Clone + Component> DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization<C> {
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::clone::<C>()

View File

@ -866,6 +866,7 @@ impl<'a> Iterator for AllocEntitiesIterator<'a> {
}
impl<'a> ExactSizeIterator for AllocEntitiesIterator<'a> {}
impl<'a> core::iter::FusedIterator for AllocEntitiesIterator<'a> {}
// SAFETY: Newly allocated entity values are unique.

View File

@ -154,6 +154,7 @@ impl<T: EntityEquivalent, const N: usize> DerefMut for UniqueEntityEquivalentArr
unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(&mut self.0) }
}
}
impl<T: EntityEquivalent> Default for UniqueEntityEquivalentArray<T, 0> {
fn default() -> Self {
Self(Default::default())
@ -527,6 +528,7 @@ impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
self.eq(&other.0)
}
}
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
PartialEq<&UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
{
@ -550,6 +552,7 @@ impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
self.eq(&other.0)
}
}
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
PartialEq<UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
{

View File

@ -1592,9 +1592,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
)]
#[should_panic]
fn non_send_resource_drop_from_different_thread() {
let mut world = World::default();
world.insert_non_send_resource(NonSendA::default());
@ -1659,7 +1657,7 @@ mod tests {
assert_eq!(q1.iter(&world).len(), 1);
assert_eq!(q2.iter(&world).len(), 1);
assert_eq!(world.entities().count_constructed(), 2);
assert_eq!(world.entity_count(), 2);
world.clear_entities();
@ -1674,7 +1672,7 @@ mod tests {
"world should not contain sparse set components"
);
assert_eq!(
world.entities().count_constructed(),
world.entity_count(),
0,
"world should not have any entities"
);
@ -2609,7 +2607,7 @@ mod tests {
}
#[test]
#[should_panic = "Recursive required components detected: A → B → C → B\nhelp: If this is intentional, consider merging the components."]
#[should_panic]
fn required_components_recursion_errors() {
#[derive(Component, Default)]
#[require(B)]
@ -2627,7 +2625,7 @@ mod tests {
}
#[test]
#[should_panic = "Recursive required components detected: A → A\nhelp: Remove require(A)."]
#[should_panic]
fn required_components_self_errors() {
#[derive(Component, Default)]
#[require(A)]

View File

@ -159,6 +159,7 @@ impl From<&str> for Name {
Name::new(name.to_owned())
}
}
impl From<String> for Name {
#[inline(always)]
fn from(name: String) -> Self {
@ -174,12 +175,14 @@ impl AsRef<str> for Name {
&self.name
}
}
impl From<&Name> for String {
#[inline(always)]
fn from(val: &Name) -> String {
val.as_str().to_owned()
}
}
impl From<Name> for String {
#[inline(always)]
fn from(val: Name) -> String {

View File

@ -0,0 +1,245 @@
//! Centralized storage for observers, allowing for efficient look-ups.
//!
//! This has multiple levels:
//! - [`World::observers`] provides access to [`Observers`], which is a central storage for all observers.
//! - [`Observers`] contains multiple distinct caches in the form of [`CachedObservers`].
//! - Most observers are looked up by the [`ComponentId`] of the event they are observing
//! - Lifecycle observers have their own fields to save lookups.
//! - [`CachedObservers`] contains maps of [`ObserverRunner`]s, which are the actual functions that will be run when the observer is triggered.
//! - These are split by target type, in order to allow for different lookup strategies.
//! - [`CachedComponentObservers`] is one of these maps, which contains observers that are specifically targeted at a component.
use bevy_platform::collections::HashMap;
use crate::{
archetype::ArchetypeFlags,
change_detection::MaybeLocation,
component::ComponentId,
entity::EntityHashMap,
observer::{ObserverRunner, ObserverTrigger},
prelude::*,
world::DeferredWorld,
};
/// An internal lookup table tracking all of the observers in the world.
///
/// Stores a cache mapping trigger ids to the registered observers.
/// Some observer kinds (like [lifecycle](crate::lifecycle) observers) have a dedicated field,
/// saving lookups for the most common triggers.
///
/// This can be accessed via [`World::observers`].
#[derive(Default, Debug)]
pub struct Observers {
// Cached ECS observers to save a lookup most common triggers.
add: CachedObservers,
insert: CachedObservers,
replace: CachedObservers,
remove: CachedObservers,
despawn: CachedObservers,
// Map from trigger type to set of observers listening to that trigger
cache: HashMap<ComponentId, CachedObservers>,
}
impl Observers {
pub(crate) fn get_observers_mut(&mut self, event_type: ComponentId) -> &mut CachedObservers {
use crate::lifecycle::*;
match event_type {
ADD => &mut self.add,
INSERT => &mut self.insert,
REPLACE => &mut self.replace,
REMOVE => &mut self.remove,
DESPAWN => &mut self.despawn,
_ => self.cache.entry(event_type).or_default(),
}
}
/// Attempts to get the observers for the given `event_type`.
///
/// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`],
/// use the [`ComponentId`] constants from the [`lifecycle`](crate::lifecycle) module.
pub fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
use crate::lifecycle::*;
match event_type {
ADD => Some(&self.add),
INSERT => Some(&self.insert),
REPLACE => Some(&self.replace),
REMOVE => Some(&self.remove),
DESPAWN => Some(&self.despawn),
_ => self.cache.get(&event_type),
}
}
/// This will run the observers of the given `event_type`, targeting the given `entity` and `components`.
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
current_target: Option<Entity>,
original_target: Option<Entity>,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T,
propagate: &mut bool,
caller: MaybeLocation,
) {
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
let (mut world, observers) = unsafe {
let world = world.as_unsafe_world_cell();
// SAFETY: There are no outstanding world references
world.increment_trigger_id();
let observers = world.observers();
let Some(observers) = observers.try_get_observers(event_type) else {
return;
};
// SAFETY: The only outstanding reference to world is `observers`
(world.into_deferred(), observers)
};
let trigger_for_components = components.clone();
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
(runner)(
world.reborrow(),
ObserverTrigger {
observer,
event_type,
components: components.clone().collect(),
current_target,
original_target,
caller,
},
data.into(),
propagate,
);
};
// Trigger observers listening for any kind of this trigger
observers
.global_observers
.iter()
.for_each(&mut trigger_observer);
// Trigger entity observers listening for this kind of trigger
if let Some(target_entity) = current_target {
if let Some(map) = observers.entity_observers.get(&target_entity) {
map.iter().for_each(&mut trigger_observer);
}
}
// Trigger observers listening to this trigger targeting a specific component
trigger_for_components.for_each(|id| {
if let Some(component_observers) = observers.component_observers.get(&id) {
component_observers
.global_observers
.iter()
.for_each(&mut trigger_observer);
if let Some(target_entity) = current_target {
if let Some(map) = component_observers
.entity_component_observers
.get(&target_entity)
{
map.iter().for_each(&mut trigger_observer);
}
}
}
});
}
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
use crate::lifecycle::*;
match event_type {
ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER),
_ => None,
}
}
pub(crate) fn update_archetype_flags(
&self,
component_id: ComponentId,
flags: &mut ArchetypeFlags,
) {
if self.add.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
}
if self.insert.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
}
if self.replace.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_REPLACE_OBSERVER);
}
if self.remove.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
}
if self.despawn.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_DESPAWN_OBSERVER);
}
}
}
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event.
///
/// This is stored inside of [`Observers`], specialized for each kind of observer.
#[derive(Default, Debug)]
pub struct CachedObservers {
// Observers listening for any time this event is fired, regardless of target
// This will also respond to events targeting specific components or entities
pub(super) global_observers: ObserverMap,
// Observers listening for this trigger fired at a specific component
pub(super) component_observers: HashMap<ComponentId, CachedComponentObservers>,
// Observers listening for this trigger fired at a specific entity
pub(super) entity_observers: EntityHashMap<ObserverMap>,
}
impl CachedObservers {
/// Returns the observers listening for this trigger, regardless of target.
/// These observers will also respond to events targeting specific components or entities.
pub fn global_observers(&self) -> &ObserverMap {
&self.global_observers
}
/// Returns the observers listening for this trigger targeting components.
pub fn get_component_observers(&self) -> &HashMap<ComponentId, CachedComponentObservers> {
&self.component_observers
}
/// Returns the observers listening for this trigger targeting entities.
pub fn entity_observers(&self) -> &HashMap<ComponentId, CachedComponentObservers> {
&self.component_observers
}
}
/// Map between an observer entity and its [`ObserverRunner`]
pub type ObserverMap = EntityHashMap<ObserverRunner>;
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component.
///
/// This is stored inside of [`CachedObservers`].
#[derive(Default, Debug)]
pub struct CachedComponentObservers {
// Observers listening to events targeting this component, but not a specific entity
pub(super) global_observers: ObserverMap,
// Observers listening to events targeting this component on a specific entity
pub(super) entity_component_observers: EntityHashMap<ObserverMap>,
}
impl CachedComponentObservers {
/// Returns the observers listening for this trigger, regardless of target.
/// These observers will also respond to events targeting specific entities.
pub fn global_observers(&self) -> &ObserverMap {
&self.global_observers
}
/// Returns the observers listening for this trigger targeting this component on a specific entity.
pub fn entity_component_observers(&self) -> &EntityHashMap<ObserverMap> {
&self.entity_component_observers
}
}

View File

@ -0,0 +1,492 @@
//! Information about observers that is stored on the entities themselves.
//!
//! This allows for easier cleanup, better inspection, and more flexible querying.
//!
//! Each observer is associated with an entity, defined by the [`Observer`] component.
//! The [`Observer`] component contains the system that will be run when the observer is triggered,
//! and the [`ObserverDescriptor`] which contains information about what the observer is observing.
//!
//! When we watch entities, we add the [`ObservedBy`] component to those entities,
//! which links back to the observer entity.
use core::any::Any;
use crate::{
component::{ComponentCloneBehavior, ComponentId, Mutable, StorageType},
entity::Entity,
error::{ErrorContext, ErrorHandler},
lifecycle::{ComponentHook, HookContext},
observer::{observer_system_runner, ObserverRunner},
prelude::*,
system::{IntoObserverSystem, ObserverSystem},
world::DeferredWorld,
};
use alloc::boxed::Box;
use alloc::vec::Vec;
use bevy_utils::prelude::DebugName;
#[cfg(feature = "bevy_reflect")]
use crate::prelude::ReflectComponent;
/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer".
///
/// 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 [`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:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// #[derive(Event)]
/// struct Speak {
/// message: String,
/// }
///
/// world.add_observer(|trigger: On<Speak>| {
/// println!("{}", trigger.event().message);
/// });
///
/// // Observers currently require a flush() to be registered. In the context of schedules,
/// // this will generally be done for you.
/// world.flush();
///
/// world.trigger(Speak {
/// message: "Hello!".into(),
/// });
/// ```
///
/// Notice that we used [`World::add_observer`]. This is just a shorthand for spawning an [`Observer`] manually:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct Speak;
/// // These are functionally the same:
/// 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:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct PrintNames;
/// # #[derive(Component, Debug)]
/// # struct Name;
/// world.add_observer(|trigger: On<PrintNames>, names: Query<&Name>| {
/// for name in &names {
/// println!("{name:?}");
/// }
/// });
/// ```
///
/// 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:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct SpawnThing;
/// # #[derive(Component, Debug)]
/// # struct Thing;
/// world.add_observer(|trigger: On<SpawnThing>, mut commands: Commands| {
/// commands.spawn(Thing);
/// });
/// ```
///
/// Observers can also trigger new events:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct A;
/// # #[derive(Event)]
/// # struct B;
/// world.add_observer(|trigger: On<A>, mut commands: Commands| {
/// commands.trigger(B);
/// });
/// ```
///
/// When the commands are flushed (including these "nested triggers") they will be
/// recursively evaluated until there are no commands left, meaning nested triggers all
/// evaluate at the same time!
///
/// 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, EntityEvent)]
/// struct Explode;
///
/// world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
/// println!("Entity {} goes BOOM!", trigger.target());
/// commands.entity(trigger.target()).despawn();
/// });
///
/// world.flush();
///
/// world.trigger_targets(Explode, entity);
/// ```
///
/// You can trigger multiple entities at once:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// world.trigger_targets(Explode, [e1, e2]);
/// ```
///
/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component, Debug)]
/// # struct Name(String);
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// world.entity_mut(e1).observe(|trigger: On<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.target()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: On<Explode>, mut commands: Commands| {
/// println!("The explosion fizzles! This entity is immune!");
/// });
/// ```
///
/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned.
/// This protects against observer "garbage" building up over time.
///
/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again)
/// just shorthand for spawning an [`Observer`] directly:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// let mut observer = Observer::new(|trigger: On<Explode>| {});
/// observer.watch_entity(entity);
/// world.spawn(observer);
/// ```
///
/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities!
///
/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`].
/// serves as the "source of truth" of the observer.
///
/// [`SystemParam`]: crate::system::SystemParam
pub struct Observer {
hook_on_add: ComponentHook,
pub(crate) error_handler: Option<ErrorHandler>,
pub(crate) system: Box<dyn AnyNamedSystem>,
pub(crate) descriptor: ObserverDescriptor,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
pub(crate) runner: ObserverRunner,
}
impl Observer {
/// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered
/// for _any_ entity (or no entity).
///
/// # Panics
///
/// Panics if the given system is an exclusive system.
pub fn new<E: Event, B: Bundle, M, I: IntoObserverSystem<E, B, M>>(system: I) -> Self {
let system = Box::new(IntoObserverSystem::into_system(system));
assert!(
!system.is_exclusive(),
concat!(
"Exclusive system `{}` may not be used as observer.\n",
"Instead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
),
system.name()
);
Self {
system,
descriptor: Default::default(),
hook_on_add: hook_on_add::<E, B, I::System>,
error_handler: None,
runner: observer_system_runner::<E, B, I::System>,
despawned_watched_entities: 0,
last_trigger_id: 0,
}
}
/// 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(IntoSystem::into_system(|| {})),
descriptor: Default::default(),
hook_on_add: |mut world, hook_context| {
let default_error_handler = world.default_error_handler();
world.commands().queue(move |world: &mut World| {
let entity = hook_context.entity;
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
if observe.descriptor.events.is_empty() {
return;
}
if observe.error_handler.is_none() {
observe.error_handler = Some(default_error_handler);
}
world.register_observer(entity);
}
});
},
error_handler: None,
runner,
despawned_watched_entities: 0,
last_trigger_id: 0,
}
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
pub fn with_entity(mut self, entity: Entity) -> Self {
self.descriptor.entities.push(entity);
self
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
/// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects.
pub fn watch_entity(&mut self, entity: Entity) {
self.descriptor.entities.push(entity);
}
/// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// with the given component target.
pub fn with_component(mut self, component: ComponentId) -> Self {
self.descriptor.components.push(component);
self
}
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
/// # Safety
/// The type of the `event` [`ComponentId`] _must_ match the actual value
/// of the event passed into the observer system.
pub unsafe fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
/// Set the error handler to use for this observer.
///
/// See the [`error` module-level documentation](crate::error) for more information.
pub fn with_error_handler(mut self, error_handler: fn(BevyError, ErrorContext)) -> Self {
self.error_handler = Some(error_handler);
self
}
/// Returns the [`ObserverDescriptor`] for this [`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 {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn on_add() -> Option<ComponentHook> {
Some(|world, context| {
let Some(observe) = world.get::<Self>(context.entity) else {
return;
};
let hook = observe.hook_on_add;
hook(world, context);
})
}
fn on_remove() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
let descriptor = core::mem::take(
&mut world
.entity_mut(entity)
.get_mut::<Self>()
.unwrap()
.as_mut()
.descriptor,
);
world.commands().queue(move |world: &mut World| {
world.unregister_observer(entity, descriptor);
});
})
}
}
/// Store information about what an [`Observer`] observes.
///
/// This information is stored inside of the [`Observer`] component,
#[derive(Default, Clone)]
pub struct ObserverDescriptor {
/// The events the observer is watching.
pub(super) events: Vec<ComponentId>,
/// The components the observer is watching.
pub(super) components: Vec<ComponentId>,
/// The entities the observer is watching.
pub(super) entities: Vec<Entity>,
}
impl ObserverDescriptor {
/// Add the given `events` to the descriptor.
/// # Safety
/// The type of each [`ComponentId`] in `events` _must_ match the actual value
/// of the event passed into the observer.
pub unsafe fn with_events(mut self, events: Vec<ComponentId>) -> Self {
self.events = events;
self
}
/// Add the given `components` to the descriptor.
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
self.components = components;
self
}
/// Add the given `entities` to the descriptor.
pub fn with_entities(mut self, entities: Vec<Entity>) -> Self {
self.entities = entities;
self
}
/// Returns the `events` that the observer is watching.
pub fn events(&self) -> &[ComponentId] {
&self.events
}
/// Returns the `components` that the observer is watching.
pub fn components(&self) -> &[ComponentId] {
&self.components
}
/// Returns the `entities` that the observer is watching.
pub fn entities(&self) -> &[Entity] {
&self.entities
}
}
/// 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.
///
/// The type parameters of this function _must_ match those used to create the [`Observer`].
/// As such, it is recommended to only use this function within the [`Observer::new`] method to
/// ensure type parameters match.
fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
mut world: DeferredWorld<'_>,
HookContext { entity, .. }: HookContext,
) {
world.commands().queue(move |world: &mut World| {
let event_id = E::register_component_id(world);
let mut components = alloc::vec![];
B::component_ids(&mut world.components_registrator(), &mut |id| {
components.push(id);
});
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 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);
}
world.register_observer(entity);
}
});
}
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
#[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;
fn on_remove() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
let observed_by = {
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
core::mem::take(&mut component.0)
};
for e in observed_by {
let (total_entities, despawned_watched_entities) = {
let Ok(mut entity_mut) = world.get_entity_mut(e) else {
continue;
};
let Some(mut state) = entity_mut.get_mut::<Observer>() else {
continue;
};
state.despawned_watched_entities += 1;
(
state.descriptor.entities.len(),
state.despawned_watched_entities as usize,
)
};
// Despawn Observer if it has no more active sources.
if total_entities == despawned_watched_entities {
world.commands().entity(e).despawn();
}
}
})
}
fn clone_behavior() -> ComponentCloneBehavior {
ComponentCloneBehavior::Ignore
}
}
pub(crate) 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()
}
}

View File

@ -1,67 +1,14 @@
//! Logic to track observers when cloning entities.
use crate::{
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
lifecycle::{ComponentHook, HookContext},
component::ComponentCloneBehavior,
entity::{ComponentCloneCtx, EntityClonerBuilder, EntityMapper, SourceComponent},
observer::ObservedBy,
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, 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;
fn on_remove() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
let observed_by = {
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
core::mem::take(&mut component.0)
};
for e in observed_by {
let (total_entities, despawned_watched_entities) = {
let Ok(mut entity_mut) = world.get_entity_mut(e) else {
continue;
};
let Some(mut state) = entity_mut.get_mut::<Observer>() else {
continue;
};
state.despawned_watched_entities += 1;
(
state.descriptor.entities.len(),
state.despawned_watched_entities as usize,
)
};
// Despawn Observer if it has no more active sources.
if total_entities == despawned_watched_entities {
world.commands().entity(e).despawn();
}
}
})
}
fn clone_behavior() -> ComponentCloneBehavior {
ComponentCloneBehavior::Ignore
}
}
impl EntityClonerBuilder<'_> {
/// Sets the option to automatically add cloned entities to the observers targeting source entity.
pub fn add_observers(&mut self, add_observers: bool) -> &mut Self {

View File

@ -129,614 +129,26 @@
//! but allows for a more ad hoc approach with observers,
//! and enables indefinite chaining of observers triggering other observers (for both better and worse!).
mod entity_observer;
mod centralized_storage;
mod distributed_storage;
mod entity_cloning;
mod runner;
mod system_param;
mod trigger_targets;
pub use entity_observer::ObservedBy;
pub use centralized_storage::*;
pub use distributed_storage::*;
pub use runner::*;
use variadics_please::all_tuples;
pub use system_param::*;
pub use trigger_targets::*;
use crate::{
archetype::ArchetypeFlags,
change_detection::MaybeLocation,
component::ComponentId,
entity::EntityHashMap,
prelude::*,
system::IntoObserverSystem,
world::{DeferredWorld, *},
};
use alloc::vec::Vec;
use bevy_platform::collections::HashMap;
use bevy_ptr::Ptr;
use core::{
fmt::Debug,
marker::PhantomData,
ops::{Deref, DerefMut},
};
use smallvec::SmallVec;
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
/// contains event propagation information. See [`On::propagate`] for more information.
///
/// The generic `B: Bundle` is used to modify the further specialize the events that this observer is interested in.
/// The entity involved *does not* have to have these components, but the observer will only be
/// triggered if the event matches the components in `B`.
///
/// This is used to to avoid providing a generic argument in your event, as is done for [`Add`]
/// and the other lifecycle events.
///
/// Providing multiple components in this bundle will cause this event to be triggered by any
/// matching component in the bundle,
/// [rather than requiring all of them to be present](https://github.com/bevyengine/bevy/issues/15325).
pub struct On<'w, E, B: Bundle = ()> {
event: &'w mut E,
propagate: &'w mut bool,
trigger: ObserverTrigger,
_marker: PhantomData<B>,
}
/// Deprecated in favor of [`On`].
#[deprecated(since = "0.17.0", note = "Renamed to `On`.")]
pub type Trigger<'w, E, B = ()> = On<'w, E, B>;
impl<'w, E, B: Bundle> On<'w, E, B> {
/// Creates a new instance of [`On`] for the given event and observer information.
pub fn new(event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger) -> Self {
Self {
event,
propagate,
trigger,
_marker: PhantomData,
}
}
/// Returns the event type of this [`On`] instance.
pub fn event_type(&self) -> ComponentId {
self.trigger.event_type
}
/// Returns a reference to the triggered event.
pub fn event(&self) -> &E {
self.event
}
/// Returns a mutable reference to the triggered event.
pub fn event_mut(&mut self) -> &mut E {
self.event
}
/// Returns a pointer to the triggered event.
pub fn event_ptr(&self) -> Ptr {
Ptr::from(&self.event)
}
/// Returns the components that triggered the observer, out of the
/// components defined in `B`. Does not necessarily include all of them as
/// `B` acts like an `OR` filter rather than an `AND` filter.
pub fn components(&self) -> &[ComponentId] {
&self.trigger.components
}
/// Returns the [`Entity`] that observed the triggered event.
/// This allows you to despawn the observer, ceasing observation.
///
/// # Examples
///
/// ```rust
/// # use bevy_ecs::prelude::{Commands, On};
/// #
/// # struct MyEvent {
/// # done: bool,
/// # }
/// #
/// /// Handle `MyEvent` and if it is done, stop observation.
/// fn my_observer(trigger: On<MyEvent>, mut commands: Commands) {
/// if trigger.event().done {
/// commands.entity(trigger.observer()).despawn();
/// return;
/// }
///
/// // ...
/// }
/// ```
pub fn observer(&self) -> Entity {
self.trigger.observer
}
/// Returns the source code location that triggered this observer.
pub fn caller(&self) -> MaybeLocation {
self.trigger.caller
}
}
impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> {
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer.
///
/// Note that if event propagation is enabled, this may not be the same as the original target of the event,
/// which can be accessed via [`On::original_target`].
///
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
pub fn target(&self) -> Entity {
self.trigger.current_target.unwrap_or(Entity::PLACEHOLDER)
}
/// Returns the original [`Entity`] that the `event` was targeted at when it was first triggered.
///
/// If event propagation is not enabled, this will always return the same value as [`On::target`].
///
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
pub fn original_target(&self) -> Entity {
self.trigger.original_target.unwrap_or(Entity::PLACEHOLDER)
}
/// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities.
///
/// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events
/// use `()` which ends the path immediately and prevents propagation.
///
/// To enable propagation, you must:
/// + Set [`EntityEvent::Traversal`] to the component you want to propagate along.
/// + Either call `propagate(true)` in the first observer or set [`EntityEvent::AUTO_PROPAGATE`] to `true`.
///
/// You can prevent an event from propagating further using `propagate(false)`.
///
/// [`Traversal`]: crate::traversal::Traversal
pub fn propagate(&mut self, should_propagate: bool) {
*self.propagate = should_propagate;
}
/// Returns the value of the flag that controls event propagation. See [`propagate`] for more information.
///
/// [`propagate`]: On::propagate
pub fn get_propagate(&self) -> bool {
*self.propagate
}
}
impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("On")
.field("event", &self.event)
.field("propagate", &self.propagate)
.field("trigger", &self.trigger)
.field("_marker", &self._marker)
.finish()
}
}
impl<'w, E, B: Bundle> Deref for On<'w, E, B> {
type Target = E;
fn deref(&self) -> &Self::Target {
self.event
}
}
impl<'w, E, B: Bundle> DerefMut for On<'w, E, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.event
}
}
/// Represents a collection of targets for a specific [`On`] instance of an [`Event`].
///
/// When an event is triggered with [`TriggerTargets`], any [`Observer`] that watches for that specific
/// event-target combination will run.
///
/// This trait is implemented for both [`Entity`] and [`ComponentId`], allowing you to target specific entities or components.
/// It is also implemented for various collections of these types, such as [`Vec`], arrays, and tuples,
/// allowing you to trigger events for multiple targets at once.
pub trait TriggerTargets {
/// The components the trigger should target.
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_;
/// The entities the trigger should target.
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_;
}
impl<T: TriggerTargets + ?Sized> TriggerTargets for &T {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
(**self).components()
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
(**self).entities()
}
}
impl TriggerTargets for Entity {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
[].into_iter()
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
core::iter::once(*self)
}
}
impl TriggerTargets for ComponentId {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
core::iter::once(*self)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
[].into_iter()
}
}
impl<T: TriggerTargets> TriggerTargets for Vec<T> {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.iter().flat_map(T::components)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
self.iter().flat_map(T::entities)
}
}
impl<const N: usize, T: TriggerTargets> TriggerTargets for [T; N] {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.iter().flat_map(T::components)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
self.iter().flat_map(T::entities)
}
}
impl<T: TriggerTargets> TriggerTargets for [T] {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.iter().flat_map(T::components)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
self.iter().flat_map(T::entities)
}
}
macro_rules! impl_trigger_targets_tuples {
($(#[$meta:meta])* $($trigger_targets: ident),*) => {
#[expect(clippy::allow_attributes, reason = "can't guarantee violation of non_snake_case")]
#[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
$(#[$meta])*
impl<$($trigger_targets: TriggerTargets),*> TriggerTargets for ($($trigger_targets,)*)
{
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
let iter = [].into_iter();
let ($($trigger_targets,)*) = self;
$(
let iter = iter.chain($trigger_targets.components());
)*
iter
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
let iter = [].into_iter();
let ($($trigger_targets,)*) = self;
$(
let iter = iter.chain($trigger_targets.entities());
)*
iter
}
}
}
}
all_tuples!(
#[doc(fake_variadic)]
impl_trigger_targets_tuples,
0,
15,
T
);
/// Store information about what an [`Observer`] observes.
///
/// This information is stored inside of the [`Observer`] component,
#[derive(Default, Clone)]
pub struct ObserverDescriptor {
/// The events the observer is watching.
events: Vec<ComponentId>,
/// The components the observer is watching.
components: Vec<ComponentId>,
/// The entities the observer is watching.
entities: Vec<Entity>,
}
impl ObserverDescriptor {
/// Add the given `events` to the descriptor.
/// # Safety
/// The type of each [`ComponentId`] in `events` _must_ match the actual value
/// of the event passed into the observer.
pub unsafe fn with_events(mut self, events: Vec<ComponentId>) -> Self {
self.events = events;
self
}
/// Add the given `components` to the descriptor.
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
self.components = components;
self
}
/// Add the given `entities` to the descriptor.
pub fn with_entities(mut self, entities: Vec<Entity>) -> Self {
self.entities = entities;
self
}
/// Returns the `events` that the observer is watching.
pub fn events(&self) -> &[ComponentId] {
&self.events
}
/// Returns the `components` that the observer is watching.
pub fn components(&self) -> &[ComponentId] {
&self.components
}
/// Returns the `entities` that the observer is watching.
pub fn entities(&self) -> &[Entity] {
&self.entities
}
}
/// Metadata about a specific [`Event`] that triggered an observer.
///
/// This information is exposed via methods on [`On`].
#[derive(Debug)]
pub struct ObserverTrigger {
/// The [`Entity`] of the observer handling the trigger.
pub observer: Entity,
/// The [`Event`] the trigger targeted.
pub event_type: ComponentId,
/// The [`ComponentId`]s the trigger targeted.
components: SmallVec<[ComponentId; 2]>,
/// The entity that the entity-event targeted, if any.
///
/// Note that if event propagation is enabled, this may not be the same as [`ObserverTrigger::original_target`].
pub current_target: Option<Entity>,
/// The entity that the entity-event was originally targeted at, if any.
///
/// If event propagation is enabled, this will be the first entity that the event was targeted at,
/// even if the event was propagated to other entities.
pub original_target: Option<Entity>,
/// The location of the source code that triggered the observer.
pub caller: MaybeLocation,
}
impl ObserverTrigger {
/// Returns the components that the trigger targeted.
pub fn components(&self) -> &[ComponentId] {
&self.components
}
}
/// Map between an observer entity and its [`ObserverRunner`]
pub type ObserverMap = EntityHashMap<ObserverRunner>;
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component.
///
/// This is stored inside of [`CachedObservers`].
#[derive(Default, Debug)]
pub struct CachedComponentObservers {
// Observers listening to events targeting this component, but not a specific entity
global_observers: ObserverMap,
// Observers listening to events targeting this component on a specific entity
entity_component_observers: EntityHashMap<ObserverMap>,
}
impl CachedComponentObservers {
/// Returns the observers listening for this trigger, regardless of target.
/// These observers will also respond to events targeting specific entities.
pub fn global_observers(&self) -> &ObserverMap {
&self.global_observers
}
/// Returns the observers listening for this trigger targeting this component on a specific entity.
pub fn entity_component_observers(&self) -> &EntityHashMap<ObserverMap> {
&self.entity_component_observers
}
}
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event.
///
/// This is stored inside of [`Observers`], specialized for each kind of observer.
#[derive(Default, Debug)]
pub struct CachedObservers {
// Observers listening for any time this event is fired, regardless of target
// This will also respond to events targeting specific components or entities
global_observers: ObserverMap,
// Observers listening for this trigger fired at a specific component
component_observers: HashMap<ComponentId, CachedComponentObservers>,
// Observers listening for this trigger fired at a specific entity
entity_observers: EntityHashMap<ObserverMap>,
}
impl CachedObservers {
/// Returns the observers listening for this trigger, regardless of target.
/// These observers will also respond to events targeting specific components or entities.
pub fn global_observers(&self) -> &ObserverMap {
&self.global_observers
}
/// Returns the observers listening for this trigger targeting components.
pub fn get_component_observers(&self) -> &HashMap<ComponentId, CachedComponentObservers> {
&self.component_observers
}
/// Returns the observers listening for this trigger targeting entities.
pub fn entity_observers(&self) -> &HashMap<ComponentId, CachedComponentObservers> {
&self.component_observers
}
}
/// An internal lookup table tracking all of the observers in the world.
///
/// Stores a cache mapping trigger ids to the registered observers.
/// Some observer kinds (like [lifecycle](crate::lifecycle) observers) have a dedicated field,
/// saving lookups for the most common triggers.
///
/// This can be accessed via [`World::observers`].
#[derive(Default, Debug)]
pub struct Observers {
// Cached ECS observers to save a lookup most common triggers.
add: CachedObservers,
insert: CachedObservers,
replace: CachedObservers,
remove: CachedObservers,
despawn: CachedObservers,
// Map from trigger type to set of observers listening to that trigger
cache: HashMap<ComponentId, CachedObservers>,
}
impl Observers {
pub(crate) fn get_observers_mut(&mut self, event_type: ComponentId) -> &mut CachedObservers {
use crate::lifecycle::*;
match event_type {
ADD => &mut self.add,
INSERT => &mut self.insert,
REPLACE => &mut self.replace,
REMOVE => &mut self.remove,
DESPAWN => &mut self.despawn,
_ => self.cache.entry(event_type).or_default(),
}
}
/// Attempts to get the observers for the given `event_type`.
///
/// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`],
/// use the [`ComponentId`] constants from the [`lifecycle`](crate::lifecycle) module.
pub fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
use crate::lifecycle::*;
match event_type {
ADD => Some(&self.add),
INSERT => Some(&self.insert),
REPLACE => Some(&self.replace),
REMOVE => Some(&self.remove),
DESPAWN => Some(&self.despawn),
_ => self.cache.get(&event_type),
}
}
/// This will run the observers of the given `event_type`, targeting the given `entity` and `components`.
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
current_target: Option<Entity>,
original_target: Option<Entity>,
components: impl Iterator<Item = ComponentId> + Clone,
data: &mut T,
propagate: &mut bool,
caller: MaybeLocation,
) {
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
let (mut world, observers) = unsafe {
let world = world.as_unsafe_world_cell();
// SAFETY: There are no outstanding world references
world.increment_trigger_id();
let observers = world.observers();
let Some(observers) = observers.try_get_observers(event_type) else {
return;
};
// SAFETY: The only outstanding reference to world is `observers`
(world.into_deferred(), observers)
};
let trigger_for_components = components.clone();
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
(runner)(
world.reborrow(),
ObserverTrigger {
observer,
event_type,
components: components.clone().collect(),
current_target,
original_target,
caller,
},
data.into(),
propagate,
);
};
// Trigger observers listening for any kind of this trigger
observers
.global_observers
.iter()
.for_each(&mut trigger_observer);
// Trigger entity observers listening for this kind of trigger
if let Some(target_entity) = current_target {
if let Some(map) = observers.entity_observers.get(&target_entity) {
map.iter().for_each(&mut trigger_observer);
}
}
// Trigger observers listening to this trigger targeting a specific component
trigger_for_components.for_each(|id| {
if let Some(component_observers) = observers.component_observers.get(&id) {
component_observers
.global_observers
.iter()
.for_each(&mut trigger_observer);
if let Some(target_entity) = current_target {
if let Some(map) = component_observers
.entity_component_observers
.get(&target_entity)
{
map.iter().for_each(&mut trigger_observer);
}
}
}
});
}
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
use crate::lifecycle::*;
match event_type {
ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER),
_ => None,
}
}
pub(crate) fn update_archetype_flags(
&self,
component_id: ComponentId,
flags: &mut ArchetypeFlags,
) {
if self.add.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
}
if self.insert.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
}
if self.replace.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_REPLACE_OBSERVER);
}
if self.remove.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
}
if self.despawn.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_DESPAWN_OBSERVER);
}
}
}
impl World {
/// Spawns a "global" [`Observer`] which will watch for the given event.
@ -1315,7 +727,7 @@ mod tests {
world.spawn(A).flush();
assert_eq!(vec!["add_2", "add_1"], world.resource::<Order>().0);
// Our A entity plus our two observers
assert_eq!(world.entities().count_constructed(), 3);
assert_eq!(world.entity_count(), 3);
}
#[test]

View File

@ -1,16 +1,10 @@
use alloc::{boxed::Box, vec};
use bevy_utils::prelude::DebugName;
//! Logic for evaluating observers, and storing functions inside of observers.
use core::any::Any;
use crate::{
component::{ComponentId, Mutable, StorageType},
error::{ErrorContext, ErrorHandler},
lifecycle::{ComponentHook, HookContext},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
system::{IntoObserverSystem, ObserverSystem},
world::DeferredWorld,
error::ErrorContext, observer::ObserverTrigger, prelude::*, query::DebugCheckedUnwrap,
system::ObserverSystem, world::DeferredWorld,
};
use bevy_ptr::PtrMut;
@ -20,323 +14,7 @@ use bevy_ptr::PtrMut;
/// but can be overridden for custom behavior.
pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: &mut bool);
/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer".
///
/// 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 [`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:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// #[derive(Event)]
/// struct Speak {
/// message: String,
/// }
///
/// world.add_observer(|trigger: On<Speak>| {
/// println!("{}", trigger.event().message);
/// });
///
/// // Observers currently require a flush() to be registered. In the context of schedules,
/// // this will generally be done for you.
/// world.flush();
///
/// world.trigger(Speak {
/// message: "Hello!".into(),
/// });
/// ```
///
/// Notice that we used [`World::add_observer`]. This is just a shorthand for spawning an [`Observer`] manually:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct Speak;
/// // These are functionally the same:
/// 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:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct PrintNames;
/// # #[derive(Component, Debug)]
/// # struct Name;
/// world.add_observer(|trigger: On<PrintNames>, names: Query<&Name>| {
/// for name in &names {
/// println!("{name:?}");
/// }
/// });
/// ```
///
/// 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:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct SpawnThing;
/// # #[derive(Component, Debug)]
/// # struct Thing;
/// world.add_observer(|trigger: On<SpawnThing>, mut commands: Commands| {
/// commands.spawn(Thing);
/// });
/// ```
///
/// Observers can also trigger new events:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct A;
/// # #[derive(Event)]
/// # struct B;
/// world.add_observer(|trigger: On<A>, mut commands: Commands| {
/// commands.trigger(B);
/// });
/// ```
///
/// When the commands are flushed (including these "nested triggers") they will be
/// recursively evaluated until there are no commands left, meaning nested triggers all
/// evaluate at the same time!
///
/// 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, EntityEvent)]
/// struct Explode;
///
/// world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
/// println!("Entity {} goes BOOM!", trigger.target());
/// commands.entity(trigger.target()).despawn();
/// });
///
/// world.flush();
///
/// world.trigger_targets(Explode, entity);
/// ```
///
/// You can trigger multiple entities at once:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// world.trigger_targets(Explode, [e1, e2]);
/// ```
///
/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component, Debug)]
/// # struct Name(String);
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// world.entity_mut(e1).observe(|trigger: On<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.target()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: On<Explode>, mut commands: Commands| {
/// println!("The explosion fizzles! This entity is immune!");
/// });
/// ```
///
/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned.
/// This protects against observer "garbage" building up over time.
///
/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again)
/// just shorthand for spawning an [`Observer`] directly:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// # #[derive(Event, EntityEvent)]
/// # struct Explode;
/// let mut observer = Observer::new(|trigger: On<Explode>| {});
/// observer.watch_entity(entity);
/// world.spawn(observer);
/// ```
///
/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities!
///
/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`].
/// serves as the "source of truth" of the observer.
///
/// [`SystemParam`]: crate::system::SystemParam
pub struct Observer {
hook_on_add: ComponentHook,
error_handler: Option<ErrorHandler>,
system: Box<dyn AnyNamedSystem>,
pub(crate) descriptor: ObserverDescriptor,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
pub(crate) runner: ObserverRunner,
}
impl Observer {
/// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered
/// for _any_ entity (or no entity).
///
/// # Panics
///
/// Panics if the given system is an exclusive system.
pub fn new<E: Event, B: Bundle, M, I: IntoObserverSystem<E, B, M>>(system: I) -> Self {
let system = Box::new(IntoObserverSystem::into_system(system));
assert!(
!system.is_exclusive(),
concat!(
"Exclusive system `{}` may not be used as observer.\n",
"Instead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
),
system.name()
);
Self {
system,
descriptor: Default::default(),
hook_on_add: hook_on_add::<E, B, I::System>,
error_handler: None,
runner: observer_system_runner::<E, B, I::System>,
despawned_watched_entities: 0,
last_trigger_id: 0,
}
}
/// 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(IntoSystem::into_system(|| {})),
descriptor: Default::default(),
hook_on_add: |mut world, hook_context| {
let default_error_handler = world.default_error_handler();
world.commands().queue(move |world: &mut World| {
let entity = hook_context.entity;
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
if observe.descriptor.events.is_empty() {
return;
}
if observe.error_handler.is_none() {
observe.error_handler = Some(default_error_handler);
}
world.register_observer(entity);
}
});
},
error_handler: None,
runner,
despawned_watched_entities: 0,
last_trigger_id: 0,
}
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
pub fn with_entity(mut self, entity: Entity) -> Self {
self.descriptor.entities.push(entity);
self
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
/// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects.
pub fn watch_entity(&mut self, entity: Entity) {
self.descriptor.entities.push(entity);
}
/// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// with the given component target.
pub fn with_component(mut self, component: ComponentId) -> Self {
self.descriptor.components.push(component);
self
}
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
/// # Safety
/// The type of the `event` [`ComponentId`] _must_ match the actual value
/// of the event passed into the observer system.
pub unsafe fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
/// Set the error handler to use for this observer.
///
/// See the [`error` module-level documentation](crate::error) for more information.
pub fn with_error_handler(mut self, error_handler: fn(BevyError, ErrorContext)) -> Self {
self.error_handler = Some(error_handler);
self
}
/// Returns the [`ObserverDescriptor`] for this [`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 {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
type Mutability = Mutable;
fn on_add() -> Option<ComponentHook> {
Some(|world, context| {
let Some(observe) = world.get::<Self>(context.entity) else {
return;
};
let hook = observe.hook_on_add;
hook(world, context);
})
}
fn on_remove() -> Option<ComponentHook> {
Some(|mut world, HookContext { entity, .. }| {
let descriptor = core::mem::take(
&mut world
.entity_mut(entity)
.get_mut::<Self>()
.unwrap()
.as_mut()
.descriptor,
);
world.commands().queue(move |world: &mut World| {
world.unregister_observer(entity, descriptor);
});
})
}
}
fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
pub(super) fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
mut world: DeferredWorld,
observer_trigger: ObserverTrigger,
ptr: PtrMut,
@ -420,48 +98,6 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
}
}
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.
///
/// The type parameters of this function _must_ match those used to create the [`Observer`].
/// As such, it is recommended to only use this function within the [`Observer::new`] method to
/// ensure type parameters match.
fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
mut world: DeferredWorld<'_>,
HookContext { entity, .. }: HookContext,
) {
world.commands().queue(move |world: &mut World| {
let event_id = E::register_component_id(world);
let mut components = vec![];
B::component_ids(&mut world.components_registrator(), &mut |id| {
components.push(id);
});
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 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);
}
world.register_observer(entity);
}
});
}
#[cfg(test)]
mod tests {
use super::*;
@ -516,9 +152,7 @@ mod tests {
}
#[test]
#[should_panic(
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."
)]
#[should_panic]
fn exclusive_system_cannot_be_observer() {
fn system(_: On<TriggerEvent>, _world: &mut World) {}
let mut world = World::default();

View File

@ -0,0 +1,206 @@
//! System parameters for working with observers.
use core::marker::PhantomData;
use core::ops::DerefMut;
use core::{fmt::Debug, ops::Deref};
use bevy_ptr::Ptr;
use smallvec::SmallVec;
use crate::{
bundle::Bundle, change_detection::MaybeLocation, component::ComponentId, event::EntityEvent,
prelude::*,
};
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
/// contains event propagation information. See [`On::propagate`] for more information.
///
/// The generic `B: Bundle` is used to modify the further specialize the events that this observer is interested in.
/// The entity involved *does not* have to have these components, but the observer will only be
/// triggered if the event matches the components in `B`.
///
/// This is used to to avoid providing a generic argument in your event, as is done for [`Add`]
/// and the other lifecycle events.
///
/// Providing multiple components in this bundle will cause this event to be triggered by any
/// matching component in the bundle,
/// [rather than requiring all of them to be present](https://github.com/bevyengine/bevy/issues/15325).
pub struct On<'w, E, B: Bundle = ()> {
event: &'w mut E,
propagate: &'w mut bool,
trigger: ObserverTrigger,
_marker: PhantomData<B>,
}
/// Deprecated in favor of [`On`].
#[deprecated(since = "0.17.0", note = "Renamed to `On`.")]
pub type Trigger<'w, E, B = ()> = On<'w, E, B>;
impl<'w, E, B: Bundle> On<'w, E, B> {
/// Creates a new instance of [`On`] for the given event and observer information.
pub fn new(event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger) -> Self {
Self {
event,
propagate,
trigger,
_marker: PhantomData,
}
}
/// Returns the event type of this [`On`] instance.
pub fn event_type(&self) -> ComponentId {
self.trigger.event_type
}
/// Returns a reference to the triggered event.
pub fn event(&self) -> &E {
self.event
}
/// Returns a mutable reference to the triggered event.
pub fn event_mut(&mut self) -> &mut E {
self.event
}
/// Returns a pointer to the triggered event.
pub fn event_ptr(&self) -> Ptr {
Ptr::from(&self.event)
}
/// Returns the components that triggered the observer, out of the
/// components defined in `B`. Does not necessarily include all of them as
/// `B` acts like an `OR` filter rather than an `AND` filter.
pub fn components(&self) -> &[ComponentId] {
&self.trigger.components
}
/// Returns the [`Entity`] that observed the triggered event.
/// This allows you to despawn the observer, ceasing observation.
///
/// # Examples
///
/// ```rust
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Event, EntityEvent)]
/// struct AssertEvent;
///
/// fn assert_observer(trigger: On<AssertEvent>) {
/// assert_eq!(trigger.observer(), trigger.target());
/// }
///
/// let mut world = World::new();
/// let observer = world.spawn(Observer::new(assert_observer)).id();
///
/// world.trigger_targets(AssertEvent, observer);
/// ```
pub fn observer(&self) -> Entity {
self.trigger.observer
}
/// Returns the source code location that triggered this observer.
pub fn caller(&self) -> MaybeLocation {
self.trigger.caller
}
}
impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> {
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer.
///
/// Note that if event propagation is enabled, this may not be the same as the original target of the event,
/// which can be accessed via [`On::original_target`].
///
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
pub fn target(&self) -> Entity {
self.trigger.current_target.unwrap_or(Entity::PLACEHOLDER)
}
/// Returns the original [`Entity`] that the `event` was targeted at when it was first triggered.
///
/// If event propagation is not enabled, this will always return the same value as [`On::target`].
///
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
pub fn original_target(&self) -> Entity {
self.trigger.original_target.unwrap_or(Entity::PLACEHOLDER)
}
/// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities.
///
/// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events
/// use `()` which ends the path immediately and prevents propagation.
///
/// To enable propagation, you must:
/// + Set [`EntityEvent::Traversal`] to the component you want to propagate along.
/// + Either call `propagate(true)` in the first observer or set [`EntityEvent::AUTO_PROPAGATE`] to `true`.
///
/// You can prevent an event from propagating further using `propagate(false)`.
///
/// [`Traversal`]: crate::traversal::Traversal
pub fn propagate(&mut self, should_propagate: bool) {
*self.propagate = should_propagate;
}
/// Returns the value of the flag that controls event propagation. See [`propagate`] for more information.
///
/// [`propagate`]: On::propagate
pub fn get_propagate(&self) -> bool {
*self.propagate
}
}
impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("On")
.field("event", &self.event)
.field("propagate", &self.propagate)
.field("trigger", &self.trigger)
.field("_marker", &self._marker)
.finish()
}
}
impl<'w, E, B: Bundle> Deref for On<'w, E, B> {
type Target = E;
fn deref(&self) -> &Self::Target {
self.event
}
}
impl<'w, E, B: Bundle> DerefMut for On<'w, E, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.event
}
}
/// Metadata about a specific [`Event`] that triggered an observer.
///
/// This information is exposed via methods on [`On`].
#[derive(Debug)]
pub struct ObserverTrigger {
/// The [`Entity`] of the observer handling the trigger.
pub observer: Entity,
/// The [`Event`] the trigger targeted.
pub event_type: ComponentId,
/// The [`ComponentId`]s the trigger targeted.
pub components: SmallVec<[ComponentId; 2]>,
/// The entity that the entity-event targeted, if any.
///
/// Note that if event propagation is enabled, this may not be the same as [`ObserverTrigger::original_target`].
pub current_target: Option<Entity>,
/// The entity that the entity-event was originally targeted at, if any.
///
/// If event propagation is enabled, this will be the first entity that the event was targeted at,
/// even if the event was propagated to other entities.
pub original_target: Option<Entity>,
/// The location of the source code that triggered the observer.
pub caller: MaybeLocation,
}
impl ObserverTrigger {
/// Returns the components that the trigger targeted.
pub fn components(&self) -> &[ComponentId] {
&self.components
}
}

View File

@ -0,0 +1,117 @@
//! Stores the [`TriggerTargets`] trait.
use crate::{component::ComponentId, prelude::*};
use alloc::vec::Vec;
use variadics_please::all_tuples;
/// Represents a collection of targets for a specific [`On`] instance of an [`Event`].
///
/// When an event is triggered with [`TriggerTargets`], any [`Observer`] that watches for that specific
/// event-target combination will run.
///
/// This trait is implemented for both [`Entity`] and [`ComponentId`], allowing you to target specific entities or components.
/// It is also implemented for various collections of these types, such as [`Vec`], arrays, and tuples,
/// allowing you to trigger events for multiple targets at once.
pub trait TriggerTargets {
/// The components the trigger should target.
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_;
/// The entities the trigger should target.
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_;
}
impl<T: TriggerTargets + ?Sized> TriggerTargets for &T {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
(**self).components()
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
(**self).entities()
}
}
impl TriggerTargets for Entity {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
[].into_iter()
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
core::iter::once(*self)
}
}
impl TriggerTargets for ComponentId {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
core::iter::once(*self)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
[].into_iter()
}
}
impl<T: TriggerTargets> TriggerTargets for Vec<T> {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.iter().flat_map(T::components)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
self.iter().flat_map(T::entities)
}
}
impl<const N: usize, T: TriggerTargets> TriggerTargets for [T; N] {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.iter().flat_map(T::components)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
self.iter().flat_map(T::entities)
}
}
impl<T: TriggerTargets> TriggerTargets for [T] {
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
self.iter().flat_map(T::components)
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
self.iter().flat_map(T::entities)
}
}
macro_rules! impl_trigger_targets_tuples {
($(#[$meta:meta])* $($trigger_targets: ident),*) => {
#[expect(clippy::allow_attributes, reason = "can't guarantee violation of non_snake_case")]
#[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
$(#[$meta])*
impl<$($trigger_targets: TriggerTargets),*> TriggerTargets for ($($trigger_targets,)*)
{
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
let iter = [].into_iter();
let ($($trigger_targets,)*) = self;
$(
let iter = iter.chain($trigger_targets.components());
)*
iter
}
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
let iter = [].into_iter();
let ($($trigger_targets,)*) = self;
$(
let iter = iter.chain($trigger_targets.entities());
)*
iter
}
}
}
}
all_tuples!(
#[doc(fake_variadic)]
impl_trigger_targets_tuples,
0,
15,
T
);

View File

@ -1489,6 +1489,7 @@ impl<T: Component> Clone for ReadFetch<'_, T> {
*self
}
}
impl<T: Component> Copy for ReadFetch<'_, T> {}
/// SAFETY:
@ -1665,6 +1666,7 @@ impl<T: Component> Clone for RefFetch<'_, T> {
*self
}
}
impl<T: Component> Copy for RefFetch<'_, T> {}
/// SAFETY:
@ -1873,6 +1875,7 @@ impl<T: Component> Clone for WriteFetch<'_, T> {
*self
}
}
impl<T: Component> Copy for WriteFetch<'_, T> {}
/// SAFETY:

View File

@ -1240,6 +1240,7 @@ unsafe impl QueryFilter for Spawned {
pub trait ArchetypeFilter: QueryFilter {}
impl<T: Component> ArchetypeFilter for With<T> {}
impl<T: Component> ArchetypeFilter for Without<T> {}
macro_rules! impl_archetype_filter_tuple {

View File

@ -507,7 +507,7 @@ mod tests {
}
#[test]
#[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."]
#[should_panic]
fn self_conflicting_worldquery() {
#[derive(QueryData)]
#[query_data(mutable)]

View File

@ -1901,9 +1901,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Transmuted state for ((&bevy_ecs::query::state::tests::A, &bevy_ecs::query::state::tests::B), ()) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
)]
#[should_panic]
fn cannot_transmute_to_include_data_not_in_original_query() {
let mut world = World::new();
world.register_component::<A>();
@ -1915,9 +1913,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Transmuted state for (&mut bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
)]
#[should_panic]
fn cannot_transmute_immut_to_mut() {
let mut world = World::new();
world.spawn(A(0));
@ -1927,9 +1923,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Transmuted state for (&bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (core::option::Option<&bevy_ecs::query::state::tests::A>, ())."
)]
#[should_panic]
fn cannot_transmute_option_to_immut() {
let mut world = World::new();
world.spawn(C(0));
@ -1941,9 +1935,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Transmuted state for (&bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (bevy_ecs::world::entity_ref::EntityRef, ())."
)]
#[should_panic]
fn cannot_transmute_entity_ref() {
let mut world = World::new();
world.register_component::<A>();
@ -2009,9 +2001,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Transmuted state for (bevy_ecs::entity::Entity, bevy_ecs::query::filter::Changed<bevy_ecs::query::state::tests::B>) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
)]
#[should_panic]
fn cannot_transmute_changed_without_access() {
let mut world = World::new();
world.register_component::<A>();
@ -2021,9 +2011,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Transmuted state for (&mut bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
)]
#[should_panic]
fn cannot_transmute_mutable_after_readonly() {
let mut world = World::new();
// Calling this method would mean we had aliasing queries.
@ -2130,9 +2118,7 @@ mod tests {
}
#[test]
#[should_panic(expected = "Joined state for (&bevy_ecs::query::state::tests::C, ()) \
attempts to access terms that are not allowed by state \
(&bevy_ecs::query::state::tests::A, ()) joined with (&bevy_ecs::query::state::tests::B, ()).")]
#[should_panic]
fn cannot_join_wrong_fetch() {
let mut world = World::new();
world.register_component::<C>();
@ -2142,12 +2128,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Joined state for (bevy_ecs::entity::Entity, bevy_ecs::query::filter::Changed<bevy_ecs::query::state::tests::C>) \
attempts to access terms that are not allowed by state \
(&bevy_ecs::query::state::tests::A, bevy_ecs::query::filter::Without<bevy_ecs::query::state::tests::C>) \
joined with (&bevy_ecs::query::state::tests::B, bevy_ecs::query::filter::Without<bevy_ecs::query::state::tests::C>)."
)]
#[should_panic]
fn cannot_join_wrong_filter() {
let mut world = World::new();
let query_1 = QueryState::<&A, Without<C>>::new(&mut world);
@ -2156,9 +2137,7 @@ mod tests {
}
#[test]
#[should_panic(
expected = "Joined state for ((&mut bevy_ecs::query::state::tests::A, &mut bevy_ecs::query::state::tests::B), ()) attempts to access terms that are not allowed by state (&bevy_ecs::query::state::tests::A, ()) joined with (&mut bevy_ecs::query::state::tests::B, ())."
)]
#[should_panic]
fn cannot_join_mutable_after_readonly() {
let mut world = World::new();
// Calling this method would mean we had aliasing queries.

View File

@ -82,6 +82,20 @@ pub trait Relationship: Component + Sized {
/// Creates this [`Relationship`] from the given `entity`.
fn from(entity: Entity) -> Self;
/// Changes the current [`Entity`] ID of the entity containing the [`RelationshipTarget`] to another one.
///
/// This is useful for updating the relationship without overwriting other fields stored in `Self`.
///
/// # Warning
///
/// This should generally not be called by user code, as modifying the related entity could invalidate the
/// relationship. If this method is used, then the hooks [`on_replace`](Relationship::on_replace) have to
/// run before and [`on_insert`](Relationship::on_insert) after it.
/// This happens automatically when this method is called with [`EntityWorldMut::modify_component`].
///
/// Prefer to use regular means of insertions when possible.
fn set_risky(&mut self, entity: Entity);
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
fn on_insert(
mut world: DeferredWorld,

View File

@ -6,7 +6,7 @@ use crate::{
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
},
system::{Commands, EntityCommands},
world::{EntityWorldMut, World},
world::{DeferredWorld, EntityWorldMut, World},
};
use bevy_platform::prelude::{Box, Vec};
use core::{marker::PhantomData, mem};
@ -42,7 +42,12 @@ impl<'w> EntityWorldMut<'w> {
let id = self.id();
self.world_scope(|world| {
for related in related {
world.entity_mut(*related).insert(R::from(id));
world
.entity_mut(*related)
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
id,
RelationshipHookMode::Run,
);
}
});
self
@ -98,7 +103,12 @@ impl<'w> EntityWorldMut<'w> {
.collection_mut_risky()
.place(*related, index);
} else {
world.entity_mut(*related).insert(R::from(id));
world
.entity_mut(*related)
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
id,
RelationshipHookMode::Run,
);
world
.get_mut::<R::RelationshipTarget>(id)
.expect("hooks should have added relationship target")
@ -165,10 +175,13 @@ impl<'w> EntityWorldMut<'w> {
}
for related in potential_relations {
// SAFETY: We'll manually be adjusting the contents of the parent to fit the final state.
// SAFETY: We'll manually be adjusting the contents of the `RelationshipTarget` to fit the final state.
world
.entity_mut(related)
.insert_with_relationship_hook_mode(R::from(id), RelationshipHookMode::Skip);
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
id,
RelationshipHookMode::Skip,
);
}
});
@ -266,7 +279,10 @@ impl<'w> EntityWorldMut<'w> {
// 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);
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
this,
RelationshipHookMode::Skip,
);
}
});
@ -352,6 +368,40 @@ impl<'w> EntityWorldMut<'w> {
self
}
fn modify_or_insert_relation_with_relationship_hook_mode<R: Relationship>(
&mut self,
entity: Entity,
relationship_hook_mode: RelationshipHookMode,
) {
// Check if the relation edge holds additional data
if size_of::<R>() > size_of::<Entity>() {
self.assert_not_despawned();
let this = self.id();
let modified = self.world_scope(|world| {
let modified = DeferredWorld::from(&mut *world)
.modify_component_with_relationship_hook_mode::<R, _>(
this,
relationship_hook_mode,
|r| r.set_risky(entity),
)
.expect("entity access must be valid")
.is_some();
world.flush();
modified
});
if modified {
return;
}
}
self.insert_with_relationship_hook_mode(R::from(entity), relationship_hook_mode);
}
}
impl<'a> EntityCommands<'a> {
@ -689,17 +739,132 @@ mod tests {
}
#[test]
fn replace_related_keeps_data() {
#[derive(Component)]
fn add_related_keeps_relationship_data() {
#[derive(Component, PartialEq, Debug)]
#[relationship(relationship_target = Parent)]
pub struct Child(Entity);
struct Child {
#[relationship]
parent: Entity,
data: u8,
}
#[derive(Component)]
#[relationship_target(relationship = Child)]
pub struct Parent {
struct Parent(Vec<Entity>);
let mut world = World::new();
let parent1 = world.spawn_empty().id();
let parent2 = world.spawn_empty().id();
let child = world
.spawn(Child {
parent: parent1,
data: 42,
})
.id();
world.entity_mut(parent2).add_related::<Child>(&[child]);
assert_eq!(
world.get::<Child>(child),
Some(&Child {
parent: parent2,
data: 42
})
);
}
#[test]
fn insert_related_keeps_relationship_data() {
#[derive(Component, PartialEq, Debug)]
#[relationship(relationship_target = Parent)]
struct Child {
#[relationship]
parent: Entity,
data: u8,
}
#[derive(Component)]
#[relationship_target(relationship = Child)]
struct Parent(Vec<Entity>);
let mut world = World::new();
let parent1 = world.spawn_empty().id();
let parent2 = world.spawn_empty().id();
let child = world
.spawn(Child {
parent: parent1,
data: 42,
})
.id();
world
.entity_mut(parent2)
.insert_related::<Child>(0, &[child]);
assert_eq!(
world.get::<Child>(child),
Some(&Child {
parent: parent2,
data: 42
})
);
}
#[test]
fn replace_related_keeps_relationship_data() {
#[derive(Component, PartialEq, Debug)]
#[relationship(relationship_target = Parent)]
struct Child {
#[relationship]
parent: Entity,
data: u8,
}
#[derive(Component)]
#[relationship_target(relationship = Child)]
struct Parent(Vec<Entity>);
let mut world = World::new();
let parent1 = world.spawn_empty().id();
let parent2 = world.spawn_empty().id();
let child = world
.spawn(Child {
parent: parent1,
data: 42,
})
.id();
world
.entity_mut(parent2)
.replace_related_with_difference::<Child>(&[], &[child], &[child]);
assert_eq!(
world.get::<Child>(child),
Some(&Child {
parent: parent2,
data: 42
})
);
world.entity_mut(parent1).replace_related::<Child>(&[child]);
assert_eq!(
world.get::<Child>(child),
Some(&Child {
parent: parent1,
data: 42
})
);
}
#[test]
fn replace_related_keeps_relationship_target_data() {
#[derive(Component)]
#[relationship(relationship_target = Parent)]
struct Child(Entity);
#[derive(Component)]
#[relationship_target(relationship = Child)]
struct Parent {
#[relationship]
children: Vec<Entity>,
pub data: u8,
data: u8,
}
let mut world = World::new();

View File

@ -26,7 +26,9 @@ pub mod passes {
#[cfg(test)]
mod tests {
use super::*;
use alloc::{string::ToString, vec, vec::Vec};
#[cfg(feature = "trace")]
use alloc::string::ToString;
use alloc::{vec, vec::Vec};
use core::sync::atomic::{AtomicU32, Ordering};
use crate::error::BevyError;
@ -770,6 +772,7 @@ mod tests {
}
mod system_ambiguity {
#[cfg(feature = "trace")]
use alloc::collections::BTreeSet;
use super::*;
@ -1110,6 +1113,7 @@ mod tests {
// Tests that the correct ambiguities were reported in the correct order.
#[test]
#[cfg(feature = "trace")]
fn correct_ambiguities() {
fn system_a(_res: ResMut<R>) {}
fn system_b(_res: ResMut<R>) {}
@ -1183,6 +1187,7 @@ mod tests {
// Test that anonymous set names work properly
// Related issue https://github.com/bevyengine/bevy/issues/9641
#[test]
#[cfg(feature = "trace")]
fn anonymous_set_name() {
let mut schedule = Schedule::new(TestSchedule);
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));

View File

@ -51,6 +51,7 @@ pub(super) trait ScheduleBuildPassObj: Send + Sync + Debug {
);
fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap<Box<dyn Any>>);
}
impl<T: ScheduleBuildPass> ScheduleBuildPassObj for T {
fn build(
&mut self,

View File

@ -235,6 +235,7 @@ pub enum Chain {
/// will be added between the successive elements.
Chained(TypeIdMap<Box<dyn Any>>),
}
impl Chain {
/// Specify that the systems must be chained.
pub fn set_chained(&mut self) {

View File

@ -210,6 +210,7 @@ unsafe impl<R: Relationship, L: SpawnableList<R> + Send + Sync + 'static> Bundle
);
}
}
impl<R: Relationship, L: SpawnableList<R>> DynamicBundle for SpawnRelatedBundle<R, L> {
type Effect = Self;

View File

@ -223,6 +223,7 @@ pub fn trigger(event: impl Event) -> impl Command {
}
/// A [`Command`] that sends an [`EntityEvent`] for the given targets.
#[track_caller]
pub fn trigger_targets(
event: impl EntityEvent,
targets: impl TriggerTargets + Send + Sync + 'static,

View File

@ -2392,7 +2392,7 @@ mod tests {
.spawn((W(1u32), W(2u64)))
.id();
command_queue.apply(&mut world);
assert_eq!(world.entities().count_constructed(), 1);
assert_eq!(world.entity_count(), 1);
let results = world
.query::<(&W<u32>, &W<u64>)>()
.iter(&world)

View File

@ -634,7 +634,7 @@ mod tests {
}
#[test]
#[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."]
#[should_panic]
fn any_of_with_mut_and_ref() {
fn sys(_: Query<AnyOf<(&mut A, &A)>>) {}
let mut world = World::default();
@ -642,7 +642,7 @@ mod tests {
}
#[test]
#[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."]
#[should_panic]
fn any_of_with_ref_and_mut() {
fn sys(_: Query<AnyOf<(&A, &mut A)>>) {}
let mut world = World::default();
@ -650,7 +650,7 @@ mod tests {
}
#[test]
#[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."]
#[should_panic]
fn any_of_with_mut_and_option() {
fn sys(_: Query<AnyOf<(&mut A, Option<&A>)>>) {}
let mut world = World::default();
@ -680,7 +680,7 @@ mod tests {
}
#[test]
#[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."]
#[should_panic]
fn any_of_with_conflicting() {
fn sys(_: Query<AnyOf<(&mut A, &mut A)>>) {}
let mut world = World::default();
@ -1629,54 +1629,42 @@ mod tests {
}
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_world_and_entity_mut_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
#[should_panic]
fn assert_world_and_entity_mut_system_does_conflict_first() {
fn system(_query: &World, _q2: Query<EntityMut>) {}
super::assert_system_does_not_conflict(system);
}
#[test]
#[should_panic(
expected = "&World conflicts with a previous mutable system parameter. Allowing this would break Rust's mutability rules"
)]
#[should_panic]
fn assert_world_and_entity_mut_system_does_conflict_second() {
fn system(_: Query<EntityMut>, _: &World) {}
super::assert_system_does_not_conflict(system);
}
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_ref_and_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
#[should_panic]
fn assert_entity_ref_and_entity_mut_system_does_conflict() {
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
super::assert_system_does_not_conflict(system);
}
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
#[should_panic]
fn assert_entity_mut_system_does_conflict() {
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
super::assert_system_does_not_conflict(system);
}
#[test]
#[should_panic(
expected = "error[B0001]: Query<EntityRef, ()> in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
)]
#[should_panic]
fn assert_deferred_world_and_entity_ref_system_does_conflict_first() {
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}
super::assert_system_does_not_conflict(system);
}
#[test]
#[should_panic(
expected = "DeferredWorld in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_second::system conflicts with a previous access."
)]
#[should_panic]
fn assert_deferred_world_and_entity_ref_system_does_conflict_second() {
fn system(_query: Query<EntityRef>, _world: DeferredWorld) {}
super::assert_system_does_not_conflict(system);

View File

@ -73,6 +73,7 @@ where
InfallibleObserverWrapper::new(IntoSystem::into_system(this))
}
}
impl<E, B, M, S> IntoObserverSystem<E, B, (Never, M), Result> for S
where
S: IntoSystem<On<'static, E, B>, Never, M> + Send + 'static,

View File

@ -410,7 +410,6 @@ pub enum RunSystemError {
mod tests {
use super::*;
use crate::prelude::*;
use alloc::string::ToString;
#[test]
fn run_system_once() {
@ -483,7 +482,5 @@ mod tests {
let result = world.run_system_once(system);
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.";
assert_eq!(expected, result.unwrap_err().to_string());
}
}

View File

@ -85,6 +85,7 @@ impl ExclusiveSystemParam for SystemName {
}
#[cfg(test)]
#[cfg(feature = "trace")]
mod tests {
use crate::{
system::{IntoSystem, RunSystemOnce, SystemName},

View File

@ -151,6 +151,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
/// let mut world = World::new();
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
/// # #[cfg(feature="Trace")] // Without debug_utils/debug enabled MyParam::foo is stripped and breaks the assert
/// assert!(err.to_string().contains(expected));
/// ```
///
@ -1399,6 +1400,7 @@ impl<'w, T> Deref for NonSend<'w, T> {
self.value
}
}
impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
fn from(nsm: NonSendMut<'a, T>) -> Self {
Self {
@ -3105,7 +3107,7 @@ mod tests {
}
#[test]
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_resource_error::res_system`: Parameter `Res<MissingResource>` failed validation: Resource does not exist"]
#[should_panic]
fn missing_resource_error() {
#[derive(Resource)]
pub struct MissingResource;
@ -3119,7 +3121,7 @@ mod tests {
}
#[test]
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_event_error::event_system`: Parameter `EventReader<MissingEvent>::events` failed validation: BufferedEvent not initialized"]
#[should_panic]
fn missing_event_error() {
use crate::prelude::{BufferedEvent, EventReader};

View File

@ -913,7 +913,6 @@ mod tests {
#[test]
fn run_system_invalid_params() {
use crate::system::RegisteredSystemError;
use alloc::{format, string::ToString};
struct T;
impl Resource for T {}
@ -928,8 +927,6 @@ mod tests {
result,
Err(RegisteredSystemError::InvalidParams { .. })
));
let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.");
assert_eq!(expected, result.unwrap_err().to_string());
}
#[test]

View File

@ -420,12 +420,12 @@ mod test {
let mut world = World::new();
queue.apply(&mut world);
assert_eq!(world.entities().count_constructed(), 2);
assert_eq!(world.entity_count(), 2);
// The previous call to `apply` cleared the queue.
// This call should do nothing.
queue.apply(&mut world);
assert_eq!(world.entities().count_constructed(), 2);
assert_eq!(world.entity_count(), 2);
}
#[expect(
@ -459,7 +459,7 @@ mod test {
queue.push(SpawnCommand);
queue.push(SpawnCommand);
queue.apply(&mut world);
assert_eq!(world.entities().count_constructed(), 3);
assert_eq!(world.entity_count(), 3);
}
#[test]

View File

@ -102,9 +102,11 @@ impl<'w> DeferredWorld<'w> {
/// If you do not need to ensure the above hooks are triggered, and your component
/// is mutable, prefer using [`get_mut`](DeferredWorld::get_mut).
#[inline]
pub(crate) fn modify_component<T: Component, R>(
#[track_caller]
pub(crate) fn modify_component_with_relationship_hook_mode<T: Component, R>(
&mut self,
entity: Entity,
relationship_hook_mode: RelationshipHookMode,
f: impl FnOnce(&mut T) -> R,
) -> Result<Option<R>, EntityMutableFetchError> {
// If the component is not registered, then it doesn't exist on this entity, so no action required.
@ -112,12 +114,17 @@ impl<'w> DeferredWorld<'w> {
return Ok(None);
};
self.modify_component_by_id(entity, component_id, move |component| {
// SAFETY: component matches the component_id collected in the above line
let mut component = unsafe { component.with_type::<T>() };
self.modify_component_by_id_with_relationship_hook_mode(
entity,
component_id,
relationship_hook_mode,
move |component| {
// SAFETY: component matches the component_id collected in the above line
let mut component = unsafe { component.with_type::<T>() };
f(&mut component)
})
f(&mut component)
},
)
}
/// Temporarily removes a [`Component`] identified by the provided
@ -132,13 +139,15 @@ impl<'w> DeferredWorld<'w> {
/// If you do not need to ensure the above hooks are triggered, and your component
/// is mutable, prefer using [`get_mut_by_id`](DeferredWorld::get_mut_by_id).
///
/// You should prefer the typed [`modify_component`](DeferredWorld::modify_component)
/// You should prefer the typed [`modify_component_with_relationship_hook_mode`](DeferredWorld::modify_component_with_relationship_hook_mode)
/// whenever possible.
#[inline]
pub(crate) fn modify_component_by_id<R>(
#[track_caller]
pub(crate) fn modify_component_by_id_with_relationship_hook_mode<R>(
&mut self,
entity: Entity,
component_id: ComponentId,
relationship_hook_mode: RelationshipHookMode,
f: impl for<'a> FnOnce(MutUntyped<'a>) -> R,
) -> Result<Option<R>, EntityMutableFetchError> {
let entity_cell = self.get_entity_mut(entity)?;
@ -162,7 +171,7 @@ impl<'w> DeferredWorld<'w> {
entity,
[component_id].into_iter(),
MaybeLocation::caller(),
RelationshipHookMode::Run,
relationship_hook_mode,
);
if archetype.has_replace_observer() {
self.trigger_observers(
@ -202,7 +211,7 @@ impl<'w> DeferredWorld<'w> {
entity,
[component_id].into_iter(),
MaybeLocation::caller(),
RelationshipHookMode::Run,
relationship_hook_mode,
);
if archetype.has_insert_observer() {
self.trigger_observers(

View File

@ -219,6 +219,14 @@ impl World {
&mut self.entities
}
/// Retrieves the number of [`Entities`] in the world.
///
/// This is helpful as a diagnostic, but it can also be used effectively in tests.
#[inline]
pub fn entity_count(&self) -> u32 {
self.entities.len()
}
/// Retrieves this world's [`Archetypes`] collection.
#[inline]
pub fn archetypes(&self) -> &Archetypes {
@ -1426,6 +1434,7 @@ impl World {
/// # assert_eq!(world.get::<Foo>(entity), Some(&Foo(true)));
/// ```
#[inline]
#[track_caller]
pub fn modify_component<T: Component, R>(
&mut self,
entity: Entity,
@ -1433,7 +1442,11 @@ impl World {
) -> Result<Option<R>, EntityMutableFetchError> {
let mut world = DeferredWorld::from(&mut *self);
let result = world.modify_component(entity, f)?;
let result = world.modify_component_with_relationship_hook_mode(
entity,
RelationshipHookMode::Run,
f,
)?;
self.flush();
Ok(result)
@ -1454,6 +1467,7 @@ impl World {
/// You should prefer the typed [`modify_component`](World::modify_component)
/// whenever possible.
#[inline]
#[track_caller]
pub fn modify_component_by_id<R>(
&mut self,
entity: Entity,
@ -1462,7 +1476,12 @@ impl World {
) -> Result<Option<R>, EntityMutableFetchError> {
let mut world = DeferredWorld::from(&mut *self);
let result = world.modify_component_by_id(entity, component_id, f)?;
let result = world.modify_component_by_id_with_relationship_hook_mode(
entity,
component_id,
RelationshipHookMode::Run,
f,
)?;
self.flush();
Ok(result)

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_encase_derive"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Bevy derive macro for encase"
homepage = "https://bevy.org"
@ -12,7 +12,7 @@ keywords = ["bevy"]
proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.16.0-dev" }
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.17.0-dev" }
encase_derive_impl = "0.10"
[lints]

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gilrs"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Gamepad system made using Gilrs for Bevy Engine"
homepage = "https://bevy.org"
@ -10,12 +10,12 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", 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_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
] }

View File

@ -47,6 +47,7 @@ pub(crate) struct Gilrs {
#[cfg(not(target_arch = "wasm32"))]
cell: SyncCell<gilrs::Gilrs>,
}
impl Gilrs {
#[inline]
pub fn with(&mut self, f: impl FnOnce(&mut gilrs::Gilrs)) {

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gizmos"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides gizmos for Bevy Engine"
homepage = "https://bevy.org"
@ -15,21 +15,21 @@ bevy_render = ["dep:bevy_render", "bevy_core_pipeline"]
[dependencies]
# Bevy
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev", optional = true }
bevy_sprite = { path = "../bevy_sprite", version = "0.16.0-dev", optional = true }
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev", optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_gizmos_macros = { path = "macros", version = "0.16.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev", optional = true }
bevy_sprite = { path = "../bevy_sprite", version = "0.17.0-dev", optional = true }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev", optional = true }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_gizmos_macros = { path = "macros", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
# other
bytemuck = "1.0"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gizmos_macros"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Derive implementations for bevy_gizmos"
homepage = "https://bevy.org"
@ -13,7 +13,7 @@ proc-macro = true
[dependencies]
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
syn = "2.0"
proc-macro2 = "1.0"

View File

@ -172,6 +172,7 @@ where
);
}
}
impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_gltf"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Bevy Engine GLTF loading"
homepage = "https://bevy.org"
@ -18,25 +18,25 @@ pbr_specular_textures = ["bevy_pbr/pbr_specular_textures"]
[dependencies]
# bevy
bevy_animation = { path = "../bevy_animation", version = "0.16.0-dev", optional = true }
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", features = [
bevy_animation = { path = "../bevy_animation", version = "0.17.0-dev", optional = true }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev", features = [
"bevy_render",
] }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
"serialize",
] }
@ -66,7 +66,7 @@ smallvec = "1.11"
tracing = { version = "0.1", default-features = false, features = ["std"] }
[dev-dependencies]
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
[lints]
workspace = true

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_image"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides image types for Bevy Engine"
homepage = "https://bevy.org"
@ -40,17 +40,17 @@ zstd = ["ruzstd"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [
"serialize",
"wgpu-types",
] }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
] }
@ -77,7 +77,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
half = { version = "2.4.1" }
[dev-dependencies]
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
[lints]
workspace = true

View File

@ -34,6 +34,7 @@ pub struct TextureAtlasSources {
/// Maps from a specific image handle to the index in `textures` where they can be found.
pub texture_ids: HashMap<AssetId<Image>, usize>,
}
impl TextureAtlasSources {
/// Retrieves the texture *section* index of the given `texture` handle.
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_input"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides input functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -60,14 +60,14 @@ libm = ["bevy_math/libm"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
"glam",
], default-features = false, optional = true }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false }
# other
serde = { version = "1", features = [

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_input_focus"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Keyboard focus management"
homepage = "https://bevy.org"
@ -60,12 +60,13 @@ libm = ["bevy_math/libm", "bevy_window/libm"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false }
bevy_window = { path = "../bevy_window", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev", default-features = false }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
"glam",
], default-features = false, optional = true }

View File

@ -147,6 +147,15 @@ pub struct FocusedInput<E: BufferedEvent + Clone> {
window: Entity,
}
/// An event which is used to set input focus. Trigger this on an entity, and it will bubble
/// until it finds a focusable entity, and then set focus to it.
#[derive(Clone, Event, EntityEvent)]
#[entity_event(traversal = WindowTraversal, auto_propagate)]
pub struct AcquireFocus {
/// The primary window entity.
window: Entity,
}
#[derive(QueryData)]
/// These are for accessing components defined on the targeted entity
pub struct WindowTraversal {
@ -172,6 +181,24 @@ impl<E: BufferedEvent + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
}
}
impl Traversal<AcquireFocus> for WindowTraversal {
fn traverse(item: Self::Item<'_, '_>, event: &AcquireFocus) -> Option<Entity> {
let WindowTraversalItem { child_of, window } = item;
// Send event to parent, if it has one.
if let Some(child_of) = child_of {
return Some(child_of.parent());
};
// Otherwise, send it to the window entity (unless this is a window entity).
if window.is_none() {
return Some(event.window);
}
None
}
}
/// Plugin which sets up systems for dispatching bubbling keyboard and gamepad button events to the focused entity.
///
/// To add bubbling to your own input events, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,

View File

@ -38,11 +38,12 @@ use bevy_input::{
keyboard::{KeyCode, KeyboardInput},
ButtonInput, ButtonState,
};
use bevy_window::PrimaryWindow;
use bevy_picking::events::{Pointer, Press};
use bevy_window::{PrimaryWindow, Window};
use log::warn;
use thiserror::Error;
use crate::{FocusedInput, InputFocus, InputFocusVisible};
use crate::{AcquireFocus, FocusedInput, InputFocus, InputFocusVisible};
#[cfg(feature = "bevy_reflect")]
use {
@ -312,6 +313,31 @@ impl TabNavigation<'_, '_> {
}
}
/// Observer which sets focus to the nearest ancestor that has tab index, using bubbling.
pub(crate) fn acquire_focus(
mut ev: On<AcquireFocus>,
focusable: Query<(), With<TabIndex>>,
windows: Query<(), With<Window>>,
mut focus: ResMut<InputFocus>,
) {
// If the entity has a TabIndex
if focusable.contains(ev.target()) {
// Stop and focus it
ev.propagate(false);
// Don't mutate unless we need to, for change detection
if focus.0 != Some(ev.target()) {
focus.0 = Some(ev.target());
}
} else if windows.contains(ev.target()) {
// Stop and clear focus
ev.propagate(false);
// Don't mutate unless we need to, for change detection
if focus.0.is_some() {
focus.clear();
}
}
}
/// Plugin for navigating between focusable entities using keyboard input.
pub struct TabNavigationPlugin;
@ -321,6 +347,8 @@ impl Plugin for TabNavigationPlugin {
#[cfg(feature = "bevy_reflect")]
app.register_type::<TabIndex>().register_type::<TabGroup>();
app.add_observer(acquire_focus);
app.add_observer(click_to_focus);
}
}
@ -330,6 +358,30 @@ fn setup_tab_navigation(mut commands: Commands, window: Query<Entity, With<Prima
}
}
fn click_to_focus(
ev: On<Pointer<Press>>,
mut focus_visible: ResMut<InputFocusVisible>,
windows: Query<Entity, With<PrimaryWindow>>,
mut commands: Commands,
) {
// Because `Pointer` is a bubbling event, we don't want to trigger an `AcquireFocus` event
// for every ancestor, but only for the original entity. Also, users may want to stop
// propagation on the pointer event at some point along the bubbling chain, so we need our
// own dedicated event whose propagation we can control.
if ev.target() == ev.original_target() {
// Clicking hides focus
if focus_visible.0 {
focus_visible.0 = false;
}
// Search for a focusable parent entity, defaulting to window if none.
if let Ok(window) = windows.single() {
commands
.entity(ev.target())
.trigger(AcquireFocus { window });
}
}
}
/// Observer function which handles tab navigation.
///
/// This observer responds to [`KeyCode::Tab`] events and Shift+Tab events,

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_internal"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
homepage = "https://bevy.org"
@ -350,80 +350,80 @@ debug = ["bevy_utils/debug"]
[dependencies]
# bevy (no_std)
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev", default-features = false }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false, features = [
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev", default-features = false }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false, features = [
bevy_input = { path = "../bevy_input", version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
"nostd-libm",
] }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"alloc",
] }
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
bevy_ptr = { path = "../bevy_ptr", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
"smallvec",
] }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev", default-features = false, features = [
bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev", default-features = false, features = [
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev", default-features = false, features = [
"bevy-support",
"bevy_reflect",
] }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
# bevy (std required)
bevy_log = { path = "../bevy_log", version = "0.16.0-dev", optional = true }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev", optional = true }
# bevy (optional)
bevy_a11y = { path = "../bevy_a11y", optional = true, version = "0.16.0-dev", features = [
bevy_a11y = { path = "../bevy_a11y", optional = true, version = "0.17.0-dev", features = [
"bevy_reflect",
] }
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.16.0-dev" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", optional = true, version = "0.16.0-dev", default-features = false, features = [
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.17.0-dev" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", optional = true, version = "0.17.0-dev", default-features = false, features = [
"alloc",
"bevy_reflect",
] }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.16.0-dev" }
bevy_core_widgets = { path = "../bevy_core_widgets", optional = true, version = "0.16.0-dev" }
bevy_anti_aliasing = { path = "../bevy_anti_aliasing", optional = true, version = "0.16.0-dev" }
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.16.0-dev" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.16.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.16.0-dev", default-features = false }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", optional = true, version = "0.16.0-dev" }
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.16.0-dev", default-features = false, features = [
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.17.0-dev" }
bevy_core_widgets = { path = "../bevy_core_widgets", optional = true, version = "0.17.0-dev" }
bevy_anti_aliasing = { path = "../bevy_anti_aliasing", optional = true, version = "0.17.0-dev" }
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.17.0-dev" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.17.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.17.0-dev", default-features = false }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", optional = true, version = "0.17.0-dev" }
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.16.0-dev" }
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.16.0-dev" }
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.16.0-dev" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.16.0-dev" }
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.16.0-dev" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.16.0-dev" }
bevy_state = { path = "../bevy_state", optional = true, version = "0.16.0-dev", default-features = false, features = [
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.17.0-dev" }
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.17.0-dev" }
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.17.0-dev" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.17.0-dev" }
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.17.0-dev" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.17.0-dev" }
bevy_state = { path = "../bevy_state", optional = true, version = "0.17.0-dev", default-features = false, features = [
"bevy_app",
"bevy_reflect",
] }
bevy_text = { path = "../bevy_text", optional = true, version = "0.16.0-dev" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", optional = true, version = "0.16.0-dev", default-features = false, features = [
bevy_text = { path = "../bevy_text", optional = true, version = "0.17.0-dev" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", optional = true, version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect",
] }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.16.0-dev", default-features = false }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.17.0-dev", default-features = false }
[lints]
workspace = true

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_log"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides logging for Bevy Engine"
homepage = "https://bevy.org"
@ -14,10 +14,10 @@ trace_tracy_memory = ["dep:tracy-client"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
# other
tracing-subscriber = { version = "0.3.1", features = [
@ -40,7 +40,7 @@ android_log-sys = "0.3.0"
[target.'cfg(target_arch = "wasm32")'.dependencies]
tracing-wasm = "0.2.1"
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
"web",
] }

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_macro_utils"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "A collection of utils for Bevy Engine"
homepage = "https://bevy.org"

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_math"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides math functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -25,7 +25,7 @@ approx = { version = "0.5", default-features = false, optional = true }
rand = { version = "0.8", default-features = false, optional = true }
rand_distr = { version = "0.4.3", optional = true }
smallvec = { version = "1.11" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
"glam",
], optional = true }
variadics_please = "1.1"

View File

@ -1347,6 +1347,7 @@ pub struct RationalSegment<P: VectorSpace> {
/// The width of the domain of this segment.
pub knot_span: f32,
}
impl<P: VectorSpace<Scalar = f32>> RationalSegment<P> {
/// Instantaneous position of a point at parametric value `t` in `[0, 1]`.
#[inline]

View File

@ -35,6 +35,7 @@ pub struct Circle {
/// The radius of the circle
pub radius: f32,
}
impl Primitive2d for Circle {}
impl Default for Circle {
@ -124,6 +125,7 @@ pub struct Arc2d {
/// Half the angle defining the arc
pub half_angle: f32,
}
impl Primitive2d for Arc2d {}
impl Default for Arc2d {
@ -290,6 +292,7 @@ pub struct CircularSector {
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
pub arc: Arc2d,
}
impl Primitive2d for CircularSector {}
impl Default for CircularSector {
@ -433,6 +436,7 @@ pub struct CircularSegment {
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
pub arc: Arc2d,
}
impl Primitive2d for CircularSegment {}
impl Default for CircularSegment {
@ -453,6 +457,7 @@ impl Measured2d for CircularSegment {
self.chord_length() + self.arc_length()
}
}
impl CircularSegment {
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
#[inline(always)]
@ -788,6 +793,7 @@ pub struct Ellipse {
/// This corresponds to the two perpendicular radii defining the ellipse.
pub half_size: Vec2,
}
impl Primitive2d for Ellipse {}
impl Default for Ellipse {
@ -939,6 +945,7 @@ pub struct Annulus {
/// The outer circle of the annulus
pub outer_circle: Circle,
}
impl Primitive2d for Annulus {}
impl Default for Annulus {
@ -1036,6 +1043,7 @@ pub struct Rhombus {
/// Size of the horizontal and vertical diagonals of the rhombus
pub half_diagonals: Vec2,
}
impl Primitive2d for Rhombus {}
impl Default for Rhombus {
@ -1171,6 +1179,7 @@ pub struct Plane2d {
/// The normal of the plane. The plane will be placed perpendicular to this direction
pub normal: Dir2,
}
impl Primitive2d for Plane2d {}
impl Default for Plane2d {
@ -1213,6 +1222,7 @@ pub struct Line2d {
/// and its opposite direction
pub direction: Dir2,
}
impl Primitive2d for Line2d {}
/// A line segment defined by two endpoints in 2D space.
@ -1232,6 +1242,7 @@ pub struct Segment2d {
/// The endpoints of the line segment.
pub vertices: [Vec2; 2],
}
impl Primitive2d for Segment2d {}
impl Segment2d {
@ -1504,6 +1515,7 @@ pub struct Polyline2d<const N: usize> {
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
pub vertices: [Vec2; N],
}
impl<const N: usize> Primitive2d for Polyline2d<N> {}
impl<const N: usize> FromIterator<Vec2> for Polyline2d<N> {
@ -1573,6 +1585,7 @@ pub struct Triangle2d {
/// The vertices of the triangle
pub vertices: [Vec2; 3],
}
impl Primitive2d for Triangle2d {}
impl Default for Triangle2d {
@ -1745,6 +1758,7 @@ pub struct Rectangle {
/// Half of the width and height of the rectangle
pub half_size: Vec2,
}
impl Primitive2d for Rectangle {}
impl Default for Rectangle {
@ -1838,6 +1852,7 @@ pub struct Polygon<const N: usize> {
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
pub vertices: [Vec2; N],
}
impl<const N: usize> Primitive2d for Polygon<N> {}
impl<const N: usize> FromIterator<Vec2> for Polygon<N> {
@ -1892,6 +1907,7 @@ pub struct ConvexPolygon<const N: usize> {
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
vertices: [Vec2; N],
}
impl<const N: usize> Primitive2d for ConvexPolygon<N> {}
/// An error that happens when creating a [`ConvexPolygon`].
@ -2013,6 +2029,7 @@ pub struct RegularPolygon {
/// The number of sides
pub sides: u32,
}
impl Primitive2d for RegularPolygon {}
impl Default for RegularPolygon {
@ -2160,6 +2177,7 @@ pub struct Capsule2d {
/// Half the height of the capsule, excluding the semicircles
pub half_length: f32,
}
impl Primitive2d for Capsule2d {}
impl Default for Capsule2d {

View File

@ -31,6 +31,7 @@ pub struct Sphere {
/// The radius of the sphere
pub radius: f32,
}
impl Primitive3d for Sphere {}
impl Default for Sphere {
@ -105,6 +106,7 @@ pub struct Plane3d {
/// Half of the width and height of the plane
pub half_size: Vec2,
}
impl Primitive3d for Plane3d {}
impl Default for Plane3d {
@ -175,6 +177,7 @@ pub struct InfinitePlane3d {
/// The normal of the plane. The plane will be placed perpendicular to this direction
pub normal: Dir3,
}
impl Primitive3d for InfinitePlane3d {}
impl Default for InfinitePlane3d {
@ -351,6 +354,7 @@ pub struct Line3d {
/// The direction of the line
pub direction: Dir3,
}
impl Primitive3d for Line3d {}
/// A line segment defined by two endpoints in 3D space.
@ -370,6 +374,7 @@ pub struct Segment3d {
/// The endpoints of the line segment.
pub vertices: [Vec3; 2],
}
impl Primitive3d for Segment3d {}
impl Segment3d {
@ -578,6 +583,7 @@ pub struct Polyline3d<const N: usize> {
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
pub vertices: [Vec3; N],
}
impl<const N: usize> Primitive3d for Polyline3d<N> {}
impl<const N: usize> FromIterator<Vec3> for Polyline3d<N> {
@ -648,6 +654,7 @@ pub struct Cuboid {
/// Half of the width, height and depth of the cuboid
pub half_size: Vec3,
}
impl Primitive3d for Cuboid {}
impl Default for Cuboid {
@ -742,6 +749,7 @@ pub struct Cylinder {
/// The half height of the cylinder
pub half_height: f32,
}
impl Primitive3d for Cylinder {}
impl Default for Cylinder {
@ -820,6 +828,7 @@ pub struct Capsule3d {
/// Half the height of the capsule, excluding the hemispheres
pub half_length: f32,
}
impl Primitive3d for Capsule3d {}
impl Default for Capsule3d {
@ -890,6 +899,7 @@ pub struct Cone {
/// The height of the cone
pub height: f32,
}
impl Primitive3d for Cone {}
impl Default for Cone {
@ -974,6 +984,7 @@ pub struct ConicalFrustum {
/// The height of the frustum
pub height: f32,
}
impl Primitive3d for ConicalFrustum {}
impl Default for ConicalFrustum {
@ -1030,6 +1041,7 @@ pub struct Torus {
#[doc(alias = "radius_of_revolution")]
pub major_radius: f32,
}
impl Primitive3d for Torus {}
impl Default for Torus {
@ -1326,6 +1338,7 @@ pub struct Tetrahedron {
/// The vertices of the tetrahedron.
pub vertices: [Vec3; 4],
}
impl Primitive3d for Tetrahedron {}
impl Default for Tetrahedron {
@ -1433,6 +1446,7 @@ pub struct Extrusion<T: Primitive2d> {
/// Half of the depth of the extrusion
pub half_depth: f32,
}
impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
impl<T: Primitive2d> Extrusion<T> {

View File

@ -34,6 +34,7 @@ struct SweepLineEvent {
/// Type of the vertex (left or right)
endpoint: Endpoint,
}
impl SweepLineEvent {
#[cfg_attr(
not(feature = "alloc"),
@ -46,17 +47,21 @@ impl SweepLineEvent {
}
}
}
impl PartialEq for SweepLineEvent {
fn eq(&self, other: &Self) -> bool {
self.position() == other.position()
}
}
impl Eq for SweepLineEvent {}
impl PartialOrd for SweepLineEvent {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for SweepLineEvent {
fn cmp(&self, other: &Self) -> Ordering {
xy_order(self.position(), other.position())
@ -129,11 +134,13 @@ struct Segment {
left: Vec2,
right: Vec2,
}
impl PartialEq for Segment {
fn eq(&self, other: &Self) -> bool {
self.edge_index == other.edge_index
}
}
impl Eq for Segment {}
impl PartialOrd for Segment {
@ -141,6 +148,7 @@ impl PartialOrd for Segment {
Some(self.cmp(other))
}
}
impl Ord for Segment {
fn cmp(&self, other: &Self) -> Ordering {
self.left

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_mesh"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides mesh types for Bevy Engine"
homepage = "https://bevy.org"
@ -10,16 +10,16 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
"serialize",
] }

View File

@ -163,6 +163,7 @@ impl Iterator for IndicesIter<'_> {
}
impl<'a> ExactSizeIterator for IndicesIter<'a> {}
impl<'a> FusedIterator for IndicesIter<'a> {}
impl From<&Indices> for IndexFormat {

View File

@ -117,6 +117,7 @@ pub struct MorphWeights {
/// The first mesh primitive assigned to these weights
first_mesh: Option<Handle<Mesh>>,
}
impl MorphWeights {
pub fn new(
weights: Vec<f32>,
@ -160,6 +161,7 @@ impl MorphWeights {
pub struct MeshMorphWeights {
weights: Vec<f32>,
}
impl MeshMorphWeights {
pub fn new(weights: Vec<f32>) -> Result<Self, MorphBuildError> {
if weights.len() > MAX_MORPH_WEIGHTS {
@ -198,6 +200,7 @@ pub struct MorphAttributes {
/// animated, as the `w` component is the sign and cannot be animated.
pub tangent: Vec3,
}
impl From<[Vec3; 3]> for MorphAttributes {
fn from([position, normal, tangent]: [Vec3; 3]) -> Self {
MorphAttributes {
@ -207,6 +210,7 @@ impl From<[Vec3; 3]> for MorphAttributes {
}
}
}
impl MorphAttributes {
/// How many components `MorphAttributes` has.
///

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_mikktspace"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
authors = [
"Benjamin Wasty <benny.wasty@gmail.com>",

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_pbr"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Adds PBR rendering to Bevy Engine"
homepage = "https://bevy.org"
@ -31,22 +31,22 @@ meshlet_processor = [
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
] }

View File

@ -535,12 +535,12 @@ pub fn extract_clusters(
continue;
}
let num_entities: usize = clusters
let entity_count: usize = clusters
.clusterable_objects
.iter()
.map(|l| l.entities.len())
.sum();
let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + num_entities);
let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + entity_count);
for cluster_objects in &clusters.clusterable_objects {
data.push(ExtractedClusterableObjectElement::ClusterHeader(
cluster_objects.counts,

View File

@ -48,6 +48,7 @@ impl Default for AmbientLight {
}
}
}
impl AmbientLight {
pub const NONE: AmbientLight = AmbientLight {
color: Color::WHITE,

View File

@ -33,6 +33,7 @@ pub enum ParallaxMappingMethod {
max_steps: u32,
},
}
impl ParallaxMappingMethod {
/// [`ParallaxMappingMethod::Relief`] with a 5 steps, a reasonable default.
pub const DEFAULT_RELIEF_MAPPING: Self = ParallaxMappingMethod::Relief { max_steps: 5 };

View File

@ -1,6 +1,6 @@
[package]
name = "bevy_picking"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides screen picking functionality for Bevy Engine"
homepage = "https://bevy.org"
@ -13,20 +13,20 @@ bevy_mesh_picking_backend = ["dep:bevy_mesh", "dep:crossbeam-channel"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", 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_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev", optional = true }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev", optional = true }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
] }

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