Merge branch 'main' into Remove-entity-reserving/pending/flushing-system
This commit is contained in:
commit
b543a4ac54
3
.github/workflows/post-release.yml
vendored
3
.github/workflows/post-release.yml
vendored
@ -49,7 +49,8 @@ jobs:
|
|||||||
--exclude ci \
|
--exclude ci \
|
||||||
--exclude errors \
|
--exclude errors \
|
||||||
--exclude bevy_mobile_example \
|
--exclude bevy_mobile_example \
|
||||||
--exclude build-wasm-example
|
--exclude build-wasm-example \
|
||||||
|
--exclude no_std_library
|
||||||
|
|
||||||
- name: Create PR
|
- name: Create PR
|
||||||
uses: peter-evans/create-pull-request@v7
|
uses: peter-evans/create-pull-request@v7
|
||||||
|
|||||||
27
Cargo.toml
27
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy"
|
name = "bevy"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
categories = ["game-engines", "graphics", "gui", "rendering"]
|
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||||
description = "A refreshingly simple data-driven game engine and app framework"
|
description = "A refreshingly simple data-driven game engine and app framework"
|
||||||
@ -72,7 +72,6 @@ allow_attributes_without_reason = "warn"
|
|||||||
|
|
||||||
[workspace.lints.rust]
|
[workspace.lints.rust]
|
||||||
missing_docs = "warn"
|
missing_docs = "warn"
|
||||||
mismatched_lifetime_syntaxes = "allow"
|
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
||||||
unsafe_code = "deny"
|
unsafe_code = "deny"
|
||||||
unsafe_op_in_unsafe_fn = "warn"
|
unsafe_op_in_unsafe_fn = "warn"
|
||||||
@ -559,12 +558,12 @@ hotpatching = ["bevy_internal/hotpatching"]
|
|||||||
debug = ["bevy_internal/debug"]
|
debug = ["bevy_internal/debug"]
|
||||||
|
|
||||||
[dependencies]
|
[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 }
|
tracing = { version = "0.1", default-features = false, optional = true }
|
||||||
|
|
||||||
# Wasm does not support dynamic linking.
|
# Wasm does not support dynamic linking.
|
||||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
[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]
|
[dev-dependencies]
|
||||||
rand = "0.8.0"
|
rand = "0.8.0"
|
||||||
@ -574,14 +573,14 @@ flate2 = "1.0"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.140"
|
||||||
bytemuck = "1.7"
|
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.
|
# 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_ecs = { path = "crates/bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_state = { path = "crates/bevy_state", version = "0.16.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.16.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.16.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.16.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.16.0-dev", default-features = false }
|
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.17.0-dev", default-features = false }
|
||||||
# Needed to poll Task examples
|
# Needed to poll Task examples
|
||||||
futures-lite = "2.0.1"
|
futures-lite = "2.0.1"
|
||||||
async-std = "1.13"
|
async-std = "1.13"
|
||||||
@ -608,7 +607,7 @@ web-sys = { version = "0.3", features = ["Window"] }
|
|||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "context_menu"
|
name = "context_menu"
|
||||||
path = "examples/usages/context_menu.rs"
|
path = "examples/usage/context_menu.rs"
|
||||||
doc-scrape-examples = true
|
doc-scrape-examples = true
|
||||||
|
|
||||||
[package.metadata.example.context_menu]
|
[package.metadata.example.context_menu]
|
||||||
@ -1285,9 +1284,9 @@ required-features = ["bevy_solari"]
|
|||||||
|
|
||||||
[package.metadata.example.solari]
|
[package.metadata.example.solari]
|
||||||
name = "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"
|
category = "3D Rendering"
|
||||||
wasm = false
|
wasm = false # Raytracing is not supported on the web
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "spherical_area_lights"
|
name = "spherical_area_lights"
|
||||||
|
|||||||
@ -49,6 +49,7 @@ impl BenchModify for Table {
|
|||||||
black_box(self.0)
|
black_box(self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BenchModify for Sparse {
|
impl BenchModify for Sparse {
|
||||||
fn bench_modify(&mut self) -> f32 {
|
fn bench_modify(&mut self) -> f32 {
|
||||||
self.0 += 1f32;
|
self.0 += 1f32;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_a11y"
|
name = "bevy_a11y"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides accessibility support for Bevy Engine"
|
description = "Provides accessibility support for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -40,10 +40,10 @@ critical-section = [
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||||
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 }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
accesskit = { version = "0.19", default-features = false }
|
accesskit = { version = "0.19", default-features = false }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_animation"
|
name = "bevy_animation"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides animation functionality for Bevy Engine"
|
description = "Provides animation functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -10,22 +10,22 @@ keywords = ["bevy"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
|
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||||
"petgraph",
|
"petgraph",
|
||||||
] }
|
] }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
"serialize",
|
"serialize",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_anti_aliasing"
|
name = "bevy_anti_aliasing"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides various anti aliasing implementations for Bevy Engine"
|
description = "Provides various anti aliasing implementations for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -16,17 +16,17 @@ smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_app"
|
name = "bevy_app"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides core App functionality for Bevy Engine"
|
description = "Provides core App functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -79,12 +79,12 @@ hotpatching = [
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||||
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 }
|
||||||
bevy_utils = { path = "../bevy_utils", 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.16.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.16.0-dev", default-features = false }
|
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
downcast-rs = { version = "2", default-features = false }
|
downcast-rs = { version = "2", default-features = false }
|
||||||
|
|||||||
@ -1582,7 +1582,7 @@ mod tests {
|
|||||||
app.add_systems(EnterMainMenu, (foo, bar));
|
app.add_systems(EnterMainMenu, (foo, bar));
|
||||||
|
|
||||||
app.world_mut().run_schedule(EnterMainMenu);
|
app.world_mut().run_schedule(EnterMainMenu);
|
||||||
assert_eq!(app.world().entities().count_constructed(), 2);
|
assert_eq!(app.world().entity_count(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -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> Eq for PropagateSet<C> {}
|
||||||
|
|
||||||
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
|
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
|
||||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||||
self._p.hash(state);
|
self._p.hash(state);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_asset"
|
name = "bevy_asset"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides asset functionality for Bevy Engine"
|
description = "Provides asset functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -19,19 +19,19 @@ watch = []
|
|||||||
trace = []
|
trace = []
|
||||||
|
|
||||||
[dependencies]
|
[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_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_asset_macros = { path = "macros", version = "0.16.0-dev" }
|
bevy_asset_macros = { path = "macros", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||||
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 = [
|
||||||
"uuid",
|
"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",
|
"async_executor",
|
||||||
] }
|
] }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ uuid = { version = "1.13.1", default-features = false, features = [
|
|||||||
tracing = { version = "0.1", default-features = false }
|
tracing = { version = "0.1", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[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]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||||
@ -78,13 +78,13 @@ web-sys = { version = "0.3", features = [
|
|||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
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",
|
"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",
|
"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",
|
"web",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_asset_macros"
|
name = "bevy_asset_macros"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Derive implementations for bevy_asset"
|
description = "Derive implementations for bevy_asset"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
syn = "2.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|||||||
@ -20,6 +20,7 @@ pub trait DirectAssetAccessExt {
|
|||||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||||
) -> Handle<A>;
|
) -> Handle<A>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectAssetAccessExt for World {
|
impl DirectAssetAccessExt for World {
|
||||||
/// Insert an asset similarly to [`Assets::add`].
|
/// Insert an asset similarly to [`Assets::add`].
|
||||||
///
|
///
|
||||||
|
|||||||
@ -56,6 +56,7 @@ pub(crate) struct EmbeddedEventHandler {
|
|||||||
dir: Dir,
|
dir: Dir,
|
||||||
last_event: Option<AssetSourceEvent>,
|
last_event: Option<AssetSourceEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilesystemEventHandler for EmbeddedEventHandler {
|
impl FilesystemEventHandler for EmbeddedEventHandler {
|
||||||
fn begin(&mut self) {
|
fn begin(&mut self) {
|
||||||
self.last_event = None;
|
self.last_event = None;
|
||||||
|
|||||||
@ -141,16 +141,19 @@ impl EmbeddedAssetRegistry {
|
|||||||
pub trait GetAssetServer {
|
pub trait GetAssetServer {
|
||||||
fn get_asset_server(&self) -> &AssetServer;
|
fn get_asset_server(&self) -> &AssetServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetAssetServer for App {
|
impl GetAssetServer for App {
|
||||||
fn get_asset_server(&self) -> &AssetServer {
|
fn get_asset_server(&self) -> &AssetServer {
|
||||||
self.world().get_asset_server()
|
self.world().get_asset_server()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetAssetServer for World {
|
impl GetAssetServer for World {
|
||||||
fn get_asset_server(&self) -> &AssetServer {
|
fn get_asset_server(&self) -> &AssetServer {
|
||||||
self.resource()
|
self.resource()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GetAssetServer for AssetServer {
|
impl GetAssetServer for AssetServer {
|
||||||
fn get_asset_server(&self) -> &AssetServer {
|
fn get_asset_server(&self) -> &AssetServer {
|
||||||
self
|
self
|
||||||
|
|||||||
@ -223,6 +223,7 @@ pub struct ReflectHandle {
|
|||||||
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
|
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
|
||||||
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
|
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReflectHandle {
|
impl ReflectHandle {
|
||||||
/// The [`TypeId`] of the asset
|
/// The [`TypeId`] of the asset
|
||||||
pub fn asset_type_id(&self) -> TypeId {
|
pub fn asset_type_id(&self) -> TypeId {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_audio"
|
name = "bevy_audio"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides audio functionality for Bevy Engine"
|
description = "Provides audio functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -10,13 +10,13 @@ keywords = ["bevy"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
|
# 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 = [
|
rodio = { version = "0.20", default-features = false, features = [
|
||||||
"wasm-bindgen",
|
"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",
|
"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",
|
"web",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,7 @@ pub struct PlaybackRemoveMarker;
|
|||||||
pub(crate) struct EarPositions<'w, 's> {
|
pub(crate) struct EarPositions<'w, 's> {
|
||||||
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
|
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w, 's> EarPositions<'w, 's> {
|
impl<'w, 's> EarPositions<'w, 's> {
|
||||||
/// Gets a set of transformed ear positions.
|
/// Gets a set of transformed ear positions.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_color"
|
name = "bevy_color"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Types for representing and manipulating color values"
|
description = "Types for representing and manipulating color values"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -10,10 +10,10 @@ keywords = ["bevy", "color"]
|
|||||||
rust-version = "1.85.0"
|
rust-version = "1.85.0"
|
||||||
|
|
||||||
[dependencies]
|
[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",
|
"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"] }
|
bytemuck = { version = "1", features = ["derive"] }
|
||||||
serde = { version = "1.0", features = [
|
serde = { version = "1.0", features = [
|
||||||
"derive",
|
"derive",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_core_pipeline"
|
name = "bevy_core_pipeline"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = [
|
authors = [
|
||||||
"Bevy Contributors <bevyengine@gmail.com>",
|
"Bevy Contributors <bevyengine@gmail.com>",
|
||||||
@ -20,20 +20,20 @@ tonemapping_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
"serialize",
|
"serialize",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@ -80,6 +80,7 @@ impl From<TextureUsages> for Camera3dDepthTextureUsage {
|
|||||||
Self(value.bits())
|
Self(value.bits())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Camera3dDepthTextureUsage> for TextureUsages {
|
impl From<Camera3dDepthTextureUsage> for TextureUsages {
|
||||||
fn from(value: Camera3dDepthTextureUsage) -> Self {
|
fn from(value: Camera3dDepthTextureUsage) -> Self {
|
||||||
Self::from_bits_truncate(value.0)
|
Self::from_bits_truncate(value.0)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_core_widgets"
|
name = "bevy_core_widgets"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Unstyled common widgets for Bevy Engine"
|
description = "Unstyled common widgets for Bevy Engine"
|
||||||
homepage = "https://bevyengine.org"
|
homepage = "https://bevyengine.org"
|
||||||
@ -10,17 +10,17 @@ keywords = ["bevy"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_a11y = { path = "../bevy_a11y", version = "0.16.0-dev" }
|
bevy_a11y = { path = "../bevy_a11y", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||||
bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" }
|
bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" }
|
||||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
|
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev", features = [
|
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
|
||||||
"bevy_ui_picking_backend",
|
"bevy_ui_picking_backend",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use accesskit::Role;
|
|||||||
use bevy_a11y::AccessibilityNode;
|
use bevy_a11y::AccessibilityNode;
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_ecs::query::Has;
|
use bevy_ecs::query::Has;
|
||||||
use bevy_ecs::system::ResMut;
|
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
@ -11,7 +10,8 @@ use bevy_ecs::{
|
|||||||
system::{Commands, Query, SystemId},
|
system::{Commands, Query, SystemId},
|
||||||
};
|
};
|
||||||
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
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_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
|
||||||
use bevy_ui::{InteractionDisabled, Pressed};
|
use bevy_ui::{InteractionDisabled, Pressed};
|
||||||
|
|
||||||
@ -36,6 +36,7 @@ fn button_on_key_event(
|
|||||||
if !disabled {
|
if !disabled {
|
||||||
let event = &trigger.event().input;
|
let event = &trigger.event().input;
|
||||||
if !event.repeat
|
if !event.repeat
|
||||||
|
&& event.state == ButtonState::Pressed
|
||||||
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
|
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
|
||||||
{
|
{
|
||||||
if let Some(on_click) = bstate.on_click {
|
if let Some(on_click) = bstate.on_click {
|
||||||
@ -65,24 +66,12 @@ fn button_on_pointer_click(
|
|||||||
fn button_on_pointer_down(
|
fn button_on_pointer_down(
|
||||||
mut trigger: On<Pointer<Press>>,
|
mut trigger: On<Pointer<Press>>,
|
||||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||||
focus: Option<ResMut<InputFocus>>,
|
|
||||||
focus_visible: Option<ResMut<InputFocusVisible>>,
|
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
||||||
trigger.propagate(false);
|
trigger.propagate(false);
|
||||||
if !disabled {
|
if !disabled && !pressed {
|
||||||
if !pressed {
|
commands.entity(button).insert(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
179
crates/bevy_core_widgets/src/core_checkbox.rs
Normal file
179
crates/bevy_core_widgets/src/core_checkbox.rs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
use accesskit::Role;
|
||||||
|
use bevy_a11y::AccessibilityNode;
|
||||||
|
use bevy_app::{App, Plugin};
|
||||||
|
use bevy_ecs::event::{EntityEvent, Event};
|
||||||
|
use bevy_ecs::query::{Has, Without};
|
||||||
|
use bevy_ecs::system::{In, ResMut};
|
||||||
|
use bevy_ecs::{
|
||||||
|
component::Component,
|
||||||
|
observer::On,
|
||||||
|
system::{Commands, Query, SystemId},
|
||||||
|
};
|
||||||
|
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
||||||
|
use bevy_input::ButtonState;
|
||||||
|
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
|
||||||
|
use bevy_picking::events::{Click, Pointer};
|
||||||
|
use bevy_ui::{Checkable, Checked, InteractionDisabled};
|
||||||
|
|
||||||
|
/// Headless widget implementation for checkboxes. The [`Checked`] component represents the current
|
||||||
|
/// state of the checkbox. The `on_change` field is an optional system id that will be run when the
|
||||||
|
/// checkbox is clicked, or when the `Enter` or `Space` key is pressed while the checkbox is
|
||||||
|
/// focused. If the `on_change` field is `None`, then instead of calling a callback, the checkbox
|
||||||
|
/// will update its own [`Checked`] state directly.
|
||||||
|
///
|
||||||
|
/// # Toggle switches
|
||||||
|
///
|
||||||
|
/// The [`CoreCheckbox`] component can be used to implement other kinds of toggle widgets. If you
|
||||||
|
/// are going to do a toggle switch, you should override the [`AccessibilityNode`] component with
|
||||||
|
/// the `Switch` role instead of the `Checkbox` role.
|
||||||
|
#[derive(Component, Debug, Default)]
|
||||||
|
#[require(AccessibilityNode(accesskit::Node::new(Role::CheckBox)), Checkable)]
|
||||||
|
pub struct CoreCheckbox {
|
||||||
|
/// One-shot system that is run when the checkbox state needs to be changed.
|
||||||
|
pub on_change: Option<SystemId<In<bool>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkbox_on_key_input(
|
||||||
|
mut ev: On<FocusedInput<KeyboardInput>>,
|
||||||
|
q_checkbox: Query<(&CoreCheckbox, Has<Checked>), Without<InteractionDisabled>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
if let Ok((checkbox, is_checked)) = q_checkbox.get(ev.target()) {
|
||||||
|
let event = &ev.event().input;
|
||||||
|
if event.state == ButtonState::Pressed
|
||||||
|
&& !event.repeat
|
||||||
|
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
|
||||||
|
{
|
||||||
|
ev.propagate(false);
|
||||||
|
set_checkbox_state(&mut commands, ev.target(), checkbox, !is_checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkbox_on_pointer_click(
|
||||||
|
mut ev: On<Pointer<Click>>,
|
||||||
|
q_checkbox: Query<(&CoreCheckbox, Has<Checked>, Has<InteractionDisabled>)>,
|
||||||
|
focus: Option<ResMut<InputFocus>>,
|
||||||
|
focus_visible: Option<ResMut<InputFocusVisible>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(ev.target()) {
|
||||||
|
// Clicking on a button makes it the focused input,
|
||||||
|
// and hides the focus ring if it was visible.
|
||||||
|
if let Some(mut focus) = focus {
|
||||||
|
focus.0 = Some(ev.target());
|
||||||
|
}
|
||||||
|
if let Some(mut focus_visible) = focus_visible {
|
||||||
|
focus_visible.0 = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ev.propagate(false);
|
||||||
|
if !disabled {
|
||||||
|
set_checkbox_state(&mut commands, ev.target(), checkbox, !is_checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Event which can be triggered on a checkbox to set the checked state. This can be used to control
|
||||||
|
/// the checkbox via gamepad buttons or other inputs.
|
||||||
|
///
|
||||||
|
/// # Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use bevy_ecs::system::Commands;
|
||||||
|
/// use bevy_core_widgets::{CoreCheckbox, SetChecked};
|
||||||
|
///
|
||||||
|
/// fn setup(mut commands: Commands) {
|
||||||
|
/// // Create a checkbox
|
||||||
|
/// let checkbox = commands.spawn((
|
||||||
|
/// CoreCheckbox::default(),
|
||||||
|
/// )).id();
|
||||||
|
///
|
||||||
|
/// // Set to checked
|
||||||
|
/// commands.trigger_targets(SetChecked(true), checkbox);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Event, EntityEvent)]
|
||||||
|
pub struct SetChecked(pub bool);
|
||||||
|
|
||||||
|
/// Event which can be triggered on a checkbox to toggle the checked state. This can be used to
|
||||||
|
/// control the checkbox via gamepad buttons or other inputs.
|
||||||
|
///
|
||||||
|
/// # Example:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use bevy_ecs::system::Commands;
|
||||||
|
/// use bevy_core_widgets::{CoreCheckbox, ToggleChecked};
|
||||||
|
///
|
||||||
|
/// fn setup(mut commands: Commands) {
|
||||||
|
/// // Create a checkbox
|
||||||
|
/// let checkbox = commands.spawn((
|
||||||
|
/// CoreCheckbox::default(),
|
||||||
|
/// )).id();
|
||||||
|
///
|
||||||
|
/// // Set to checked
|
||||||
|
/// commands.trigger_targets(ToggleChecked, checkbox);
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Event, EntityEvent)]
|
||||||
|
pub struct ToggleChecked;
|
||||||
|
|
||||||
|
fn checkbox_on_set_checked(
|
||||||
|
mut ev: On<SetChecked>,
|
||||||
|
q_checkbox: Query<(&CoreCheckbox, Has<Checked>, Has<InteractionDisabled>)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(ev.target()) {
|
||||||
|
ev.propagate(false);
|
||||||
|
if disabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let will_be_checked = ev.event().0;
|
||||||
|
if will_be_checked != is_checked {
|
||||||
|
set_checkbox_state(&mut commands, ev.target(), checkbox, will_be_checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn checkbox_on_toggle_checked(
|
||||||
|
mut ev: On<ToggleChecked>,
|
||||||
|
q_checkbox: Query<(&CoreCheckbox, Has<Checked>, Has<InteractionDisabled>)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
if let Ok((checkbox, is_checked, disabled)) = q_checkbox.get(ev.target()) {
|
||||||
|
ev.propagate(false);
|
||||||
|
if disabled {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_checkbox_state(&mut commands, ev.target(), checkbox, !is_checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_checkbox_state(
|
||||||
|
commands: &mut Commands,
|
||||||
|
entity: impl Into<bevy_ecs::entity::Entity>,
|
||||||
|
checkbox: &CoreCheckbox,
|
||||||
|
new_state: bool,
|
||||||
|
) {
|
||||||
|
if let Some(on_change) = checkbox.on_change {
|
||||||
|
commands.run_system_with(on_change, new_state);
|
||||||
|
} else if new_state {
|
||||||
|
commands.entity(entity.into()).insert(Checked);
|
||||||
|
} else {
|
||||||
|
commands.entity(entity.into()).remove::<Checked>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plugin that adds the observers for the [`CoreCheckbox`] widget.
|
||||||
|
pub struct CoreCheckboxPlugin;
|
||||||
|
|
||||||
|
impl Plugin for CoreCheckboxPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_observer(checkbox_on_key_input)
|
||||||
|
.add_observer(checkbox_on_pointer_click)
|
||||||
|
.add_observer(checkbox_on_set_checked)
|
||||||
|
.add_observer(checkbox_on_toggle_checked);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,12 +3,11 @@ use core::ops::RangeInclusive;
|
|||||||
use accesskit::{Orientation, Role};
|
use accesskit::{Orientation, Role};
|
||||||
use bevy_a11y::AccessibilityNode;
|
use bevy_a11y::AccessibilityNode;
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_ecs::entity::Entity;
|
|
||||||
use bevy_ecs::event::{EntityEvent, Event};
|
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::lifecycle::Insert;
|
||||||
use bevy_ecs::query::Has;
|
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::world::DeferredWorld;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
component::Component,
|
||||||
@ -18,7 +17,7 @@ use bevy_ecs::{
|
|||||||
};
|
};
|
||||||
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
||||||
use bevy_input::ButtonState;
|
use bevy_input::ButtonState;
|
||||||
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
|
use bevy_input_focus::FocusedInput;
|
||||||
use bevy_log::warn_once;
|
use bevy_log::warn_once;
|
||||||
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
|
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
|
||||||
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};
|
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_thumb: Query<&ComputedNode, With<CoreSliderThumb>>,
|
||||||
q_children: Query<&Children>,
|
q_children: Query<&Children>,
|
||||||
q_parents: Query<&ChildOf>,
|
|
||||||
focus: Option<ResMut<InputFocus>>,
|
|
||||||
focus_visible: Option<ResMut<InputFocusVisible>>,
|
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
ui_scale: Res<UiScale>,
|
ui_scale: Res<UiScale>,
|
||||||
) {
|
) {
|
||||||
if q_thumb.contains(trigger.target()) {
|
if q_thumb.contains(trigger.target()) {
|
||||||
// Thumb click, stop propagation to prevent track click.
|
// Thumb click, stop propagation to prevent track click.
|
||||||
trigger.propagate(false);
|
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)) =
|
} else if let Ok((slider, value, range, step, node, node_target, transform, disabled)) =
|
||||||
q_slider.get(trigger.target())
|
q_slider.get(trigger.target())
|
||||||
{
|
{
|
||||||
// Track click
|
// Track click
|
||||||
trigger.propagate(false);
|
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 {
|
if disabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,11 +15,13 @@
|
|||||||
// the widget level, like `SliderValue`, should not have the `Core` prefix.
|
// the widget level, like `SliderValue`, should not have the `Core` prefix.
|
||||||
|
|
||||||
mod core_button;
|
mod core_button;
|
||||||
|
mod core_checkbox;
|
||||||
mod core_slider;
|
mod core_slider;
|
||||||
|
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
|
|
||||||
pub use core_button::{CoreButton, CoreButtonPlugin};
|
pub use core_button::{CoreButton, CoreButtonPlugin};
|
||||||
|
pub use core_checkbox::{CoreCheckbox, CoreCheckboxPlugin, SetChecked, ToggleChecked};
|
||||||
pub use core_slider::{
|
pub use core_slider::{
|
||||||
CoreSlider, CoreSliderDragState, CoreSliderPlugin, CoreSliderThumb, SetSliderValue,
|
CoreSlider, CoreSliderDragState, CoreSliderPlugin, CoreSliderThumb, SetSliderValue,
|
||||||
SliderRange, SliderStep, SliderValue, TrackClick,
|
SliderRange, SliderStep, SliderValue, TrackClick,
|
||||||
@ -31,6 +33,6 @@ pub struct CoreWidgetsPlugin;
|
|||||||
|
|
||||||
impl Plugin for CoreWidgetsPlugin {
|
impl Plugin for CoreWidgetsPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins((CoreButtonPlugin, CoreSliderPlugin));
|
app.add_plugins((CoreButtonPlugin, CoreCheckboxPlugin, CoreSliderPlugin));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_derive"
|
name = "bevy_derive"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides derive implementations for Bevy Engine"
|
description = "Provides derive implementations for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
quote = "1.0"
|
||||||
syn = { version = "2.0", features = ["full"] }
|
syn = { version = "2.0", features = ["full"] }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_dev_tools"
|
name = "bevy_dev_tools"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Collection of developer tools for the Bevy Engine"
|
description = "Collection of developer tools for the Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -13,21 +13,21 @@ bevy_ci_testing = ["serde", "ron"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||||
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
|
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||||
bevy_text = { path = "../bevy_text", version = "0.16.0-dev" }
|
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
|
||||||
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev" }
|
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||||
bevy_state = { path = "../bevy_state", version = "0.16.0-dev" }
|
bevy_state = { path = "../bevy_state", version = "0.17.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_diagnostic"
|
name = "bevy_diagnostic"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides diagnostic functionality for Bevy Engine"
|
description = "Provides diagnostic functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -53,12 +53,12 @@ critical-section = [
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.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.16.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.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.16.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.16.0-dev", default-features = false, features = [
|
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -17,11 +17,13 @@ pub struct FrameTimeDiagnosticsPlugin {
|
|||||||
/// The smoothing factor for the exponential moving average. Usually `2.0 / (history_length + 1.0)`.
|
/// The smoothing factor for the exponential moving average. Usually `2.0 / (history_length + 1.0)`.
|
||||||
pub smoothing_factor: f64,
|
pub smoothing_factor: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FrameTimeDiagnosticsPlugin {
|
impl Default for FrameTimeDiagnosticsPlugin {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(DEFAULT_MAX_HISTORY_LENGTH)
|
Self::new(DEFAULT_MAX_HISTORY_LENGTH)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrameTimeDiagnosticsPlugin {
|
impl FrameTimeDiagnosticsPlugin {
|
||||||
/// Creates a new `FrameTimeDiagnosticsPlugin` with the specified `max_history_length` and a
|
/// Creates a new `FrameTimeDiagnosticsPlugin` with the specified `max_history_length` and a
|
||||||
/// reasonable `smoothing_factor`.
|
/// reasonable `smoothing_factor`.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_dylib"
|
name = "bevy_dylib"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Force the Bevy Engine to be dynamically linked for faster linking"
|
description = "Force the Bevy Engine to be dynamically linked for faster linking"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
|||||||
crate-type = ["dylib"]
|
crate-type = ["dylib"]
|
||||||
|
|
||||||
[dependencies]
|
[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]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_ecs"
|
name = "bevy_ecs"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Bevy Engine's entity component system"
|
description = "Bevy Engine's entity component system"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -86,14 +86,14 @@ critical-section = [
|
|||||||
hotpatching = ["dep:subsecond"]
|
hotpatching = ["dep:subsecond"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" }
|
bevy_ptr = { path = "../bevy_ptr", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
], default-features = false, optional = true }
|
], default-features = false, optional = true }
|
||||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.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.16.0-dev" }
|
bevy_ecs_macros = { path = "macros", version = "0.17.0-dev" }
|
||||||
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",
|
"alloc",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_ecs_macros"
|
name = "bevy_ecs_macros"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
description = "Bevy ECS Macros"
|
description = "Bevy ECS Macros"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[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"] }
|
syn = { version = "2.0.99", features = ["full", "extra-traits"] }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
|
|||||||
@ -792,6 +792,11 @@ fn derive_relationship(
|
|||||||
#relationship_member: entity
|
#relationship_member: entity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_risky(&mut self, entity: Entity) {
|
||||||
|
self.#relationship_member = entity;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,7 @@ use crate::{
|
|||||||
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
|
use bevy_macro_utils::{derive_label, ensure_no_collision, get_struct_fields, BevyManifest};
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::{Ident, Span};
|
use proc_macro2::{Ident, Span};
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote, ToTokens};
|
||||||
use syn::{
|
use syn::{
|
||||||
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
|
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
|
||||||
ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam,
|
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());
|
let mut field_kind = Vec::with_capacity(named_fields.len());
|
||||||
|
|
||||||
for field in named_fields {
|
for field in named_fields {
|
||||||
|
let mut kind = BundleFieldKind::Component;
|
||||||
|
|
||||||
for attr in field
|
for attr in field
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
@ -86,7 +88,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||||||
{
|
{
|
||||||
if let Err(error) = attr.parse_nested_meta(|meta| {
|
if let Err(error) = attr.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
||||||
field_kind.push(BundleFieldKind::Ignore);
|
kind = BundleFieldKind::Ignore;
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(meta.error(format!(
|
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
|
let field = named_fields
|
||||||
@ -111,82 +113,33 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||||||
.map(|field| &field.ty)
|
.map(|field| &field.ty)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let mut field_component_ids = Vec::new();
|
let mut active_field_types = Vec::new();
|
||||||
let mut field_get_component_ids = Vec::new();
|
let mut active_field_tokens = Vec::new();
|
||||||
let mut field_get_components = Vec::new();
|
let mut inactive_field_tokens = Vec::new();
|
||||||
let mut field_from_components = Vec::new();
|
|
||||||
let mut field_required_components = Vec::new();
|
|
||||||
for (((i, field_type), field_kind), field) in field_type
|
for (((i, field_type), field_kind), field) in field_type
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.zip(field_kind.iter())
|
.zip(field_kind.iter())
|
||||||
.zip(field.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 {
|
match field_kind {
|
||||||
BundleFieldKind::Component => {
|
BundleFieldKind::Component => {
|
||||||
field_component_ids.push(quote! {
|
active_field_types.push(field_type);
|
||||||
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, &mut *ids);
|
active_field_tokens.push(field_tokens);
|
||||||
});
|
|
||||||
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),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BundleFieldKind::Ignore => {
|
BundleFieldKind::Ignore => inactive_field_tokens.push(field_tokens),
|
||||||
field_from_components.push(quote! {
|
|
||||||
#field: ::core::default::Default::default(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let generics = ast.generics;
|
let generics = ast.generics;
|
||||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||||
let struct_name = &ast.ident;
|
let struct_name = &ast.ident;
|
||||||
|
|
||||||
let from_components = attributes.impl_from_components.then(|| quote! {
|
let bundle_impl = quote! {
|
||||||
// SAFETY:
|
|
||||||
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
|
|
||||||
#[allow(deprecated)]
|
|
||||||
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
|
|
||||||
#[allow(unused_variables, non_snake_case)]
|
|
||||||
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
|
|
||||||
where
|
|
||||||
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
|
|
||||||
{
|
|
||||||
Self{
|
|
||||||
#(#field_from_components)*
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let attribute_errors = &errors;
|
|
||||||
|
|
||||||
TokenStream::from(quote! {
|
|
||||||
#(#attribute_errors)*
|
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
|
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
|
||||||
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
|
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
|
||||||
@ -196,27 +149,27 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||||||
fn component_ids(
|
fn component_ids(
|
||||||
components: &mut #ecs_path::component::ComponentsRegistrator,
|
components: &mut #ecs_path::component::ComponentsRegistrator,
|
||||||
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
|
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(
|
fn get_component_ids(
|
||||||
components: &#ecs_path::component::Components,
|
components: &#ecs_path::component::Components,
|
||||||
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
|
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(
|
fn register_required_components(
|
||||||
components: &mut #ecs_path::component::ComponentsRegistrator,
|
components: &mut #ecs_path::component::ComponentsRegistrator,
|
||||||
required_components: &mut #ecs_path::component::RequiredComponents
|
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)]
|
#[allow(deprecated)]
|
||||||
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
||||||
type Effect = ();
|
type Effect = ();
|
||||||
@ -226,9 +179,36 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
|||||||
self,
|
self,
|
||||||
func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
|
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
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -83,7 +83,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
|
|||||||
let user_generics_with_world_and_state = {
|
let user_generics_with_world_and_state = {
|
||||||
let mut generics = ast.generics;
|
let mut generics = ast.generics;
|
||||||
generics.params.insert(0, parse_quote!('__w));
|
generics.params.insert(0, parse_quote!('__w));
|
||||||
generics.params.insert(0, parse_quote!('__s));
|
generics.params.insert(1, parse_quote!('__s));
|
||||||
generics
|
generics
|
||||||
};
|
};
|
||||||
let (
|
let (
|
||||||
|
|||||||
@ -960,6 +960,7 @@ impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {
|
|||||||
&self.archetypes[index.start.0.index()..]
|
&self.archetypes[index.start.0.index()..]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<ArchetypeId> for Archetypes {
|
impl Index<ArchetypeId> for Archetypes {
|
||||||
type Output = Archetype;
|
type Output = Archetype;
|
||||||
|
|
||||||
|
|||||||
@ -2410,4 +2410,13 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(world.resource::<Count>().0, 3);
|
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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -623,6 +623,7 @@ pub trait ComponentMutability: private::Seal + 'static {
|
|||||||
pub struct Immutable;
|
pub struct Immutable;
|
||||||
|
|
||||||
impl private::Seal for Immutable {}
|
impl private::Seal for Immutable {}
|
||||||
|
|
||||||
impl ComponentMutability for Immutable {
|
impl ComponentMutability for Immutable {
|
||||||
const MUTABLE: bool = false;
|
const MUTABLE: bool = false;
|
||||||
}
|
}
|
||||||
@ -633,6 +634,7 @@ impl ComponentMutability for Immutable {
|
|||||||
pub struct Mutable;
|
pub struct Mutable;
|
||||||
|
|
||||||
impl private::Seal for Mutable {}
|
impl private::Seal for Mutable {}
|
||||||
|
|
||||||
impl ComponentMutability for Mutable {
|
impl ComponentMutability for Mutable {
|
||||||
const MUTABLE: bool = true;
|
const MUTABLE: bool = true;
|
||||||
}
|
}
|
||||||
@ -2968,6 +2970,7 @@ impl<T> Default for DefaultCloneBehaviorSpecialization<T> {
|
|||||||
pub trait DefaultCloneBehaviorBase {
|
pub trait DefaultCloneBehaviorBase {
|
||||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
|
impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
|
||||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||||
ComponentCloneBehavior::Default
|
ComponentCloneBehavior::Default
|
||||||
@ -2979,6 +2982,7 @@ impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
|
|||||||
pub trait DefaultCloneBehaviorViaClone {
|
pub trait DefaultCloneBehaviorViaClone {
|
||||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Clone + Component> DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization<C> {
|
impl<C: Clone + Component> DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization<C> {
|
||||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||||
ComponentCloneBehavior::clone::<C>()
|
ComponentCloneBehavior::clone::<C>()
|
||||||
|
|||||||
@ -866,6 +866,7 @@ impl<'a> Iterator for AllocEntitiesIterator<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ExactSizeIterator for AllocEntitiesIterator<'a> {}
|
impl<'a> ExactSizeIterator for AllocEntitiesIterator<'a> {}
|
||||||
|
|
||||||
impl<'a> core::iter::FusedIterator for AllocEntitiesIterator<'a> {}
|
impl<'a> core::iter::FusedIterator for AllocEntitiesIterator<'a> {}
|
||||||
|
|
||||||
// SAFETY: Newly allocated entity values are unique.
|
// SAFETY: Newly allocated entity values are unique.
|
||||||
|
|||||||
@ -154,6 +154,7 @@ impl<T: EntityEquivalent, const N: usize> DerefMut for UniqueEntityEquivalentArr
|
|||||||
unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(&mut self.0) }
|
unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(&mut self.0) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: EntityEquivalent> Default for UniqueEntityEquivalentArray<T, 0> {
|
impl<T: EntityEquivalent> Default for UniqueEntityEquivalentArray<T, 0> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(Default::default())
|
Self(Default::default())
|
||||||
@ -527,6 +528,7 @@ impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
|||||||
self.eq(&other.0)
|
self.eq(&other.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
||||||
PartialEq<&UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
|
PartialEq<&UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
|
||||||
{
|
{
|
||||||
@ -550,6 +552,7 @@ impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
|||||||
self.eq(&other.0)
|
self.eq(&other.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
||||||
PartialEq<UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
|
PartialEq<UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1592,9 +1592,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic]
|
||||||
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
|
|
||||||
)]
|
|
||||||
fn non_send_resource_drop_from_different_thread() {
|
fn non_send_resource_drop_from_different_thread() {
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
world.insert_non_send_resource(NonSendA::default());
|
world.insert_non_send_resource(NonSendA::default());
|
||||||
@ -1659,7 +1657,7 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(q1.iter(&world).len(), 1);
|
assert_eq!(q1.iter(&world).len(), 1);
|
||||||
assert_eq!(q2.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();
|
world.clear_entities();
|
||||||
|
|
||||||
@ -1674,7 +1672,7 @@ mod tests {
|
|||||||
"world should not contain sparse set components"
|
"world should not contain sparse set components"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
world.entities().count_constructed(),
|
world.entity_count(),
|
||||||
0,
|
0,
|
||||||
"world should not have any entities"
|
"world should not have any entities"
|
||||||
);
|
);
|
||||||
@ -2609,7 +2607,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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() {
|
fn required_components_recursion_errors() {
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
#[require(B)]
|
#[require(B)]
|
||||||
@ -2627,7 +2625,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic = "Recursive required components detected: A → A\nhelp: Remove require(A)."]
|
#[should_panic]
|
||||||
fn required_components_self_errors() {
|
fn required_components_self_errors() {
|
||||||
#[derive(Component, Default)]
|
#[derive(Component, Default)]
|
||||||
#[require(A)]
|
#[require(A)]
|
||||||
|
|||||||
@ -159,6 +159,7 @@ impl From<&str> for Name {
|
|||||||
Name::new(name.to_owned())
|
Name::new(name.to_owned())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for Name {
|
impl From<String> for Name {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(name: String) -> Self {
|
fn from(name: String) -> Self {
|
||||||
@ -174,12 +175,14 @@ impl AsRef<str> for Name {
|
|||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Name> for String {
|
impl From<&Name> for String {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(val: &Name) -> String {
|
fn from(val: &Name) -> String {
|
||||||
val.as_str().to_owned()
|
val.as_str().to_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Name> for String {
|
impl From<Name> for String {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from(val: Name) -> String {
|
fn from(val: Name) -> String {
|
||||||
|
|||||||
245
crates/bevy_ecs/src/observer/centralized_storage.rs
Normal file
245
crates/bevy_ecs/src/observer/centralized_storage.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
492
crates/bevy_ecs/src/observer/distributed_storage.rs
Normal file
492
crates/bevy_ecs/src/observer/distributed_storage.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,67 +1,14 @@
|
|||||||
|
//! Logic to track observers when cloning entities.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
|
component::ComponentCloneBehavior,
|
||||||
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
entity::{ComponentCloneCtx, EntityClonerBuilder, EntityMapper, SourceComponent},
|
||||||
lifecycle::{ComponentHook, HookContext},
|
observer::ObservedBy,
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_reflect")]
|
|
||||||
use crate::prelude::ReflectComponent;
|
|
||||||
|
|
||||||
use super::Observer;
|
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<'_> {
|
impl EntityClonerBuilder<'_> {
|
||||||
/// Sets the option to automatically add cloned entities to the observers targeting source entity.
|
/// 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 {
|
pub fn add_observers(&mut self, add_observers: bool) -> &mut Self {
|
||||||
@ -129,614 +129,26 @@
|
|||||||
//! but allows for a more ad hoc approach with observers,
|
//! but allows for a more ad hoc approach with observers,
|
||||||
//! and enables indefinite chaining of observers triggering other observers (for both better and worse!).
|
//! 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 runner;
|
||||||
|
mod system_param;
|
||||||
|
mod trigger_targets;
|
||||||
|
|
||||||
pub use entity_observer::ObservedBy;
|
pub use centralized_storage::*;
|
||||||
|
pub use distributed_storage::*;
|
||||||
pub use runner::*;
|
pub use runner::*;
|
||||||
use variadics_please::all_tuples;
|
pub use system_param::*;
|
||||||
|
pub use trigger_targets::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
archetype::ArchetypeFlags,
|
|
||||||
change_detection::MaybeLocation,
|
change_detection::MaybeLocation,
|
||||||
component::ComponentId,
|
component::ComponentId,
|
||||||
entity::EntityHashMap,
|
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::IntoObserverSystem,
|
system::IntoObserverSystem,
|
||||||
world::{DeferredWorld, *},
|
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 {
|
impl World {
|
||||||
/// Spawns a "global" [`Observer`] which will watch for the given event.
|
/// Spawns a "global" [`Observer`] which will watch for the given event.
|
||||||
@ -1315,7 +727,7 @@ mod tests {
|
|||||||
world.spawn(A).flush();
|
world.spawn(A).flush();
|
||||||
assert_eq!(vec!["add_2", "add_1"], world.resource::<Order>().0);
|
assert_eq!(vec!["add_2", "add_1"], world.resource::<Order>().0);
|
||||||
// Our A entity plus our two observers
|
// Our A entity plus our two observers
|
||||||
assert_eq!(world.entities().count_constructed(), 3);
|
assert_eq!(world.entity_count(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -1,16 +1,10 @@
|
|||||||
use alloc::{boxed::Box, vec};
|
//! Logic for evaluating observers, and storing functions inside of observers.
|
||||||
use bevy_utils::prelude::DebugName;
|
|
||||||
use core::any::Any;
|
use core::any::Any;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{ComponentId, Mutable, StorageType},
|
error::ErrorContext, observer::ObserverTrigger, prelude::*, query::DebugCheckedUnwrap,
|
||||||
error::{ErrorContext, ErrorHandler},
|
system::ObserverSystem, world::DeferredWorld,
|
||||||
lifecycle::{ComponentHook, HookContext},
|
|
||||||
observer::{ObserverDescriptor, ObserverTrigger},
|
|
||||||
prelude::*,
|
|
||||||
query::DebugCheckedUnwrap,
|
|
||||||
system::{IntoObserverSystem, ObserverSystem},
|
|
||||||
world::DeferredWorld,
|
|
||||||
};
|
};
|
||||||
use bevy_ptr::PtrMut;
|
use bevy_ptr::PtrMut;
|
||||||
|
|
||||||
@ -20,323 +14,7 @@ use bevy_ptr::PtrMut;
|
|||||||
/// but can be overridden for custom behavior.
|
/// but can be overridden for custom behavior.
|
||||||
pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: &mut bool);
|
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".
|
pub(super) fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||||
///
|
|
||||||
/// 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>>(
|
|
||||||
mut world: DeferredWorld,
|
mut world: DeferredWorld,
|
||||||
observer_trigger: ObserverTrigger,
|
observer_trigger: ObserverTrigger,
|
||||||
ptr: PtrMut,
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -516,9 +152,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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."
|
|
||||||
)]
|
|
||||||
fn exclusive_system_cannot_be_observer() {
|
fn exclusive_system_cannot_be_observer() {
|
||||||
fn system(_: On<TriggerEvent>, _world: &mut World) {}
|
fn system(_: On<TriggerEvent>, _world: &mut World) {}
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
|
|||||||
206
crates/bevy_ecs/src/observer/system_param.rs
Normal file
206
crates/bevy_ecs/src/observer/system_param.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
117
crates/bevy_ecs/src/observer/trigger_targets.rs
Normal file
117
crates/bevy_ecs/src/observer/trigger_targets.rs
Normal 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
|
||||||
|
);
|
||||||
@ -1489,6 +1489,7 @@ impl<T: Component> Clone for ReadFetch<'_, T> {
|
|||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component> Copy for ReadFetch<'_, T> {}
|
impl<T: Component> Copy for ReadFetch<'_, T> {}
|
||||||
|
|
||||||
/// SAFETY:
|
/// SAFETY:
|
||||||
@ -1665,6 +1666,7 @@ impl<T: Component> Clone for RefFetch<'_, T> {
|
|||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component> Copy for RefFetch<'_, T> {}
|
impl<T: Component> Copy for RefFetch<'_, T> {}
|
||||||
|
|
||||||
/// SAFETY:
|
/// SAFETY:
|
||||||
@ -1873,6 +1875,7 @@ impl<T: Component> Clone for WriteFetch<'_, T> {
|
|||||||
*self
|
*self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Component> Copy for WriteFetch<'_, T> {}
|
impl<T: Component> Copy for WriteFetch<'_, T> {}
|
||||||
|
|
||||||
/// SAFETY:
|
/// SAFETY:
|
||||||
|
|||||||
@ -1240,6 +1240,7 @@ unsafe impl QueryFilter for Spawned {
|
|||||||
pub trait ArchetypeFilter: QueryFilter {}
|
pub trait ArchetypeFilter: QueryFilter {}
|
||||||
|
|
||||||
impl<T: Component> ArchetypeFilter for With<T> {}
|
impl<T: Component> ArchetypeFilter for With<T> {}
|
||||||
|
|
||||||
impl<T: Component> ArchetypeFilter for Without<T> {}
|
impl<T: Component> ArchetypeFilter for Without<T> {}
|
||||||
|
|
||||||
macro_rules! impl_archetype_filter_tuple {
|
macro_rules! impl_archetype_filter_tuple {
|
||||||
|
|||||||
@ -507,7 +507,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."]
|
#[should_panic]
|
||||||
fn self_conflicting_worldquery() {
|
fn self_conflicting_worldquery() {
|
||||||
#[derive(QueryData)]
|
#[derive(QueryData)]
|
||||||
#[query_data(mutable)]
|
#[query_data(mutable)]
|
||||||
|
|||||||
@ -1901,9 +1901,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_transmute_to_include_data_not_in_original_query() {
|
fn cannot_transmute_to_include_data_not_in_original_query() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.register_component::<A>();
|
world.register_component::<A>();
|
||||||
@ -1915,9 +1913,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_transmute_immut_to_mut() {
|
fn cannot_transmute_immut_to_mut() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.spawn(A(0));
|
world.spawn(A(0));
|
||||||
@ -1927,9 +1923,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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>, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_transmute_option_to_immut() {
|
fn cannot_transmute_option_to_immut() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.spawn(C(0));
|
world.spawn(C(0));
|
||||||
@ -1941,9 +1935,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_transmute_entity_ref() {
|
fn cannot_transmute_entity_ref() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.register_component::<A>();
|
world.register_component::<A>();
|
||||||
@ -2009,9 +2001,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_transmute_changed_without_access() {
|
fn cannot_transmute_changed_without_access() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.register_component::<A>();
|
world.register_component::<A>();
|
||||||
@ -2021,9 +2011,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_transmute_mutable_after_readonly() {
|
fn cannot_transmute_mutable_after_readonly() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
// Calling this method would mean we had aliasing queries.
|
// Calling this method would mean we had aliasing queries.
|
||||||
@ -2130,9 +2118,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Joined state for (&bevy_ecs::query::state::tests::C, ()) \
|
#[should_panic]
|
||||||
attempts to access terms that are not allowed by state \
|
|
||||||
(&bevy_ecs::query::state::tests::A, ()) joined with (&bevy_ecs::query::state::tests::B, ()).")]
|
|
||||||
fn cannot_join_wrong_fetch() {
|
fn cannot_join_wrong_fetch() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
world.register_component::<C>();
|
world.register_component::<C>();
|
||||||
@ -2142,12 +2128,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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>)."
|
|
||||||
)]
|
|
||||||
fn cannot_join_wrong_filter() {
|
fn cannot_join_wrong_filter() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
let query_1 = QueryState::<&A, Without<C>>::new(&mut world);
|
let query_1 = QueryState::<&A, Without<C>>::new(&mut world);
|
||||||
@ -2156,9 +2137,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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, ())."
|
|
||||||
)]
|
|
||||||
fn cannot_join_mutable_after_readonly() {
|
fn cannot_join_mutable_after_readonly() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
// Calling this method would mean we had aliasing queries.
|
// Calling this method would mean we had aliasing queries.
|
||||||
|
|||||||
@ -82,6 +82,20 @@ pub trait Relationship: Component + Sized {
|
|||||||
/// Creates this [`Relationship`] from the given `entity`.
|
/// Creates this [`Relationship`] from the given `entity`.
|
||||||
fn from(entity: Entity) -> Self;
|
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.
|
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||||
fn on_insert(
|
fn on_insert(
|
||||||
mut world: DeferredWorld,
|
mut world: DeferredWorld,
|
||||||
|
|||||||
@ -6,7 +6,7 @@ use crate::{
|
|||||||
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
||||||
},
|
},
|
||||||
system::{Commands, EntityCommands},
|
system::{Commands, EntityCommands},
|
||||||
world::{EntityWorldMut, World},
|
world::{DeferredWorld, EntityWorldMut, World},
|
||||||
};
|
};
|
||||||
use bevy_platform::prelude::{Box, Vec};
|
use bevy_platform::prelude::{Box, Vec};
|
||||||
use core::{marker::PhantomData, mem};
|
use core::{marker::PhantomData, mem};
|
||||||
@ -42,7 +42,12 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
let id = self.id();
|
let id = self.id();
|
||||||
self.world_scope(|world| {
|
self.world_scope(|world| {
|
||||||
for related in related {
|
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
|
self
|
||||||
@ -98,7 +103,12 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
.collection_mut_risky()
|
.collection_mut_risky()
|
||||||
.place(*related, index);
|
.place(*related, index);
|
||||||
} else {
|
} 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
|
world
|
||||||
.get_mut::<R::RelationshipTarget>(id)
|
.get_mut::<R::RelationshipTarget>(id)
|
||||||
.expect("hooks should have added relationship target")
|
.expect("hooks should have added relationship target")
|
||||||
@ -165,10 +175,13 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for related in potential_relations {
|
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
|
world
|
||||||
.entity_mut(related)
|
.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
|
// We changed the target collection manually so don't run the insert hook
|
||||||
world
|
world
|
||||||
.entity_mut(*new_relation)
|
.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
|
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> {
|
impl<'a> EntityCommands<'a> {
|
||||||
@ -689,17 +739,132 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn replace_related_keeps_data() {
|
fn add_related_keeps_relationship_data() {
|
||||||
#[derive(Component)]
|
#[derive(Component, PartialEq, Debug)]
|
||||||
#[relationship(relationship_target = Parent)]
|
#[relationship(relationship_target = Parent)]
|
||||||
pub struct Child(Entity);
|
struct Child {
|
||||||
|
#[relationship]
|
||||||
|
parent: Entity,
|
||||||
|
data: u8,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
#[relationship_target(relationship = Child)]
|
#[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]
|
#[relationship]
|
||||||
children: Vec<Entity>,
|
children: Vec<Entity>,
|
||||||
pub data: u8,
|
data: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
|||||||
@ -26,7 +26,9 @@ pub mod passes {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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 core::sync::atomic::{AtomicU32, Ordering};
|
||||||
|
|
||||||
use crate::error::BevyError;
|
use crate::error::BevyError;
|
||||||
@ -770,6 +772,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod system_ambiguity {
|
mod system_ambiguity {
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
use alloc::collections::BTreeSet;
|
use alloc::collections::BTreeSet;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -1110,6 +1113,7 @@ mod tests {
|
|||||||
|
|
||||||
// Tests that the correct ambiguities were reported in the correct order.
|
// Tests that the correct ambiguities were reported in the correct order.
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
fn correct_ambiguities() {
|
fn correct_ambiguities() {
|
||||||
fn system_a(_res: ResMut<R>) {}
|
fn system_a(_res: ResMut<R>) {}
|
||||||
fn system_b(_res: ResMut<R>) {}
|
fn system_b(_res: ResMut<R>) {}
|
||||||
@ -1183,6 +1187,7 @@ mod tests {
|
|||||||
// Test that anonymous set names work properly
|
// Test that anonymous set names work properly
|
||||||
// Related issue https://github.com/bevyengine/bevy/issues/9641
|
// Related issue https://github.com/bevyengine/bevy/issues/9641
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
fn anonymous_set_name() {
|
fn anonymous_set_name() {
|
||||||
let mut schedule = Schedule::new(TestSchedule);
|
let mut schedule = Schedule::new(TestSchedule);
|
||||||
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
|
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
|
||||||
|
|||||||
@ -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>>);
|
fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap<Box<dyn Any>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: ScheduleBuildPass> ScheduleBuildPassObj for T {
|
impl<T: ScheduleBuildPass> ScheduleBuildPassObj for T {
|
||||||
fn build(
|
fn build(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
@ -235,6 +235,7 @@ pub enum Chain {
|
|||||||
/// will be added between the successive elements.
|
/// will be added between the successive elements.
|
||||||
Chained(TypeIdMap<Box<dyn Any>>),
|
Chained(TypeIdMap<Box<dyn Any>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Chain {
|
impl Chain {
|
||||||
/// Specify that the systems must be chained.
|
/// Specify that the systems must be chained.
|
||||||
pub fn set_chained(&mut self) {
|
pub fn set_chained(&mut self) {
|
||||||
|
|||||||
@ -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> {
|
impl<R: Relationship, L: SpawnableList<R>> DynamicBundle for SpawnRelatedBundle<R, L> {
|
||||||
type Effect = Self;
|
type Effect = Self;
|
||||||
|
|
||||||
|
|||||||
@ -223,6 +223,7 @@ pub fn trigger(event: impl Event) -> impl Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A [`Command`] that sends an [`EntityEvent`] for the given targets.
|
/// A [`Command`] that sends an [`EntityEvent`] for the given targets.
|
||||||
|
#[track_caller]
|
||||||
pub fn trigger_targets(
|
pub fn trigger_targets(
|
||||||
event: impl EntityEvent,
|
event: impl EntityEvent,
|
||||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||||
|
|||||||
@ -2392,7 +2392,7 @@ mod tests {
|
|||||||
.spawn((W(1u32), W(2u64)))
|
.spawn((W(1u32), W(2u64)))
|
||||||
.id();
|
.id();
|
||||||
command_queue.apply(&mut world);
|
command_queue.apply(&mut world);
|
||||||
assert_eq!(world.entities().count_constructed(), 1);
|
assert_eq!(world.entity_count(), 1);
|
||||||
let results = world
|
let results = world
|
||||||
.query::<(&W<u32>, &W<u64>)>()
|
.query::<(&W<u32>, &W<u64>)>()
|
||||||
.iter(&world)
|
.iter(&world)
|
||||||
|
|||||||
@ -634,7 +634,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 any_of_with_mut_and_ref() {
|
||||||
fn sys(_: Query<AnyOf<(&mut A, &A)>>) {}
|
fn sys(_: Query<AnyOf<(&mut A, &A)>>) {}
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -642,7 +642,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 any_of_with_ref_and_mut() {
|
||||||
fn sys(_: Query<AnyOf<(&A, &mut A)>>) {}
|
fn sys(_: Query<AnyOf<(&A, &mut A)>>) {}
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -650,7 +650,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 any_of_with_mut_and_option() {
|
||||||
fn sys(_: Query<AnyOf<(&mut A, Option<&A>)>>) {}
|
fn sys(_: Query<AnyOf<(&mut A, Option<&A>)>>) {}
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -680,7 +680,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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 any_of_with_conflicting() {
|
||||||
fn sys(_: Query<AnyOf<(&mut A, &mut A)>>) {}
|
fn sys(_: Query<AnyOf<(&mut A, &mut A)>>) {}
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -1629,54 +1629,42 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic]
|
||||||
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_world_and_entity_mut_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
|
||||||
)]
|
|
||||||
fn assert_world_and_entity_mut_system_does_conflict_first() {
|
fn assert_world_and_entity_mut_system_does_conflict_first() {
|
||||||
fn system(_query: &World, _q2: Query<EntityMut>) {}
|
fn system(_query: &World, _q2: Query<EntityMut>) {}
|
||||||
super::assert_system_does_not_conflict(system);
|
super::assert_system_does_not_conflict(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic]
|
||||||
expected = "&World conflicts with a previous mutable system parameter. Allowing this would break Rust's mutability rules"
|
|
||||||
)]
|
|
||||||
fn assert_world_and_entity_mut_system_does_conflict_second() {
|
fn assert_world_and_entity_mut_system_does_conflict_second() {
|
||||||
fn system(_: Query<EntityMut>, _: &World) {}
|
fn system(_: Query<EntityMut>, _: &World) {}
|
||||||
super::assert_system_does_not_conflict(system);
|
super::assert_system_does_not_conflict(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic]
|
||||||
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_ref_and_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
|
||||||
)]
|
|
||||||
fn assert_entity_ref_and_entity_mut_system_does_conflict() {
|
fn assert_entity_ref_and_entity_mut_system_does_conflict() {
|
||||||
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
|
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
|
||||||
super::assert_system_does_not_conflict(system);
|
super::assert_system_does_not_conflict(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic]
|
||||||
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
|
||||||
)]
|
|
||||||
fn assert_entity_mut_system_does_conflict() {
|
fn assert_entity_mut_system_does_conflict() {
|
||||||
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
|
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
|
||||||
super::assert_system_does_not_conflict(system);
|
super::assert_system_does_not_conflict(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[should_panic]
|
||||||
expected = "error[B0001]: Query<EntityRef, ()> in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
|
||||||
)]
|
|
||||||
fn assert_deferred_world_and_entity_ref_system_does_conflict_first() {
|
fn assert_deferred_world_and_entity_ref_system_does_conflict_first() {
|
||||||
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}
|
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}
|
||||||
super::assert_system_does_not_conflict(system);
|
super::assert_system_does_not_conflict(system);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(
|
#[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."
|
|
||||||
)]
|
|
||||||
fn assert_deferred_world_and_entity_ref_system_does_conflict_second() {
|
fn assert_deferred_world_and_entity_ref_system_does_conflict_second() {
|
||||||
fn system(_query: Query<EntityRef>, _world: DeferredWorld) {}
|
fn system(_query: Query<EntityRef>, _world: DeferredWorld) {}
|
||||||
super::assert_system_does_not_conflict(system);
|
super::assert_system_does_not_conflict(system);
|
||||||
|
|||||||
@ -73,6 +73,7 @@ where
|
|||||||
InfallibleObserverWrapper::new(IntoSystem::into_system(this))
|
InfallibleObserverWrapper::new(IntoSystem::into_system(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E, B, M, S> IntoObserverSystem<E, B, (Never, M), Result> for S
|
impl<E, B, M, S> IntoObserverSystem<E, B, (Never, M), Result> for S
|
||||||
where
|
where
|
||||||
S: IntoSystem<On<'static, E, B>, Never, M> + Send + 'static,
|
S: IntoSystem<On<'static, E, B>, Never, M> + Send + 'static,
|
||||||
|
|||||||
@ -410,7 +410,6 @@ pub enum RunSystemError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use alloc::string::ToString;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_system_once() {
|
fn run_system_once() {
|
||||||
@ -483,7 +482,5 @@ mod tests {
|
|||||||
let result = world.run_system_once(system);
|
let result = world.run_system_once(system);
|
||||||
|
|
||||||
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
|
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
|
||||||
let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -85,6 +85,7 @@ impl ExclusiveSystemParam for SystemName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
system::{IntoSystem, RunSystemOnce, SystemName},
|
system::{IntoSystem, RunSystemOnce, SystemName},
|
||||||
|
|||||||
@ -151,6 +151,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
|
|||||||
/// let mut world = World::new();
|
/// let mut world = World::new();
|
||||||
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
|
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
|
||||||
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
|
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
|
||||||
|
/// # #[cfg(feature="Trace")] // Without debug_utils/debug enabled MyParam::foo is stripped and breaks the assert
|
||||||
/// assert!(err.to_string().contains(expected));
|
/// assert!(err.to_string().contains(expected));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@ -1399,6 +1400,7 @@ impl<'w, T> Deref for NonSend<'w, T> {
|
|||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
|
impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
|
||||||
fn from(nsm: NonSendMut<'a, T>) -> Self {
|
fn from(nsm: NonSendMut<'a, T>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -3105,7 +3107,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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() {
|
fn missing_resource_error() {
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
pub struct MissingResource;
|
pub struct MissingResource;
|
||||||
@ -3119,7 +3121,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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() {
|
fn missing_event_error() {
|
||||||
use crate::prelude::{BufferedEvent, EventReader};
|
use crate::prelude::{BufferedEvent, EventReader};
|
||||||
|
|
||||||
|
|||||||
@ -913,7 +913,6 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn run_system_invalid_params() {
|
fn run_system_invalid_params() {
|
||||||
use crate::system::RegisteredSystemError;
|
use crate::system::RegisteredSystemError;
|
||||||
use alloc::{format, string::ToString};
|
|
||||||
|
|
||||||
struct T;
|
struct T;
|
||||||
impl Resource for T {}
|
impl Resource for T {}
|
||||||
@ -928,8 +927,6 @@ mod tests {
|
|||||||
result,
|
result,
|
||||||
Err(RegisteredSystemError::InvalidParams { .. })
|
Err(RegisteredSystemError::InvalidParams { .. })
|
||||||
));
|
));
|
||||||
let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\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]
|
#[test]
|
||||||
|
|||||||
@ -420,12 +420,12 @@ mod test {
|
|||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
queue.apply(&mut world);
|
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.
|
// The previous call to `apply` cleared the queue.
|
||||||
// This call should do nothing.
|
// This call should do nothing.
|
||||||
queue.apply(&mut world);
|
queue.apply(&mut world);
|
||||||
assert_eq!(world.entities().count_constructed(), 2);
|
assert_eq!(world.entity_count(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(
|
#[expect(
|
||||||
@ -459,7 +459,7 @@ mod test {
|
|||||||
queue.push(SpawnCommand);
|
queue.push(SpawnCommand);
|
||||||
queue.push(SpawnCommand);
|
queue.push(SpawnCommand);
|
||||||
queue.apply(&mut world);
|
queue.apply(&mut world);
|
||||||
assert_eq!(world.entities().count_constructed(), 3);
|
assert_eq!(world.entity_count(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -102,9 +102,11 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
/// If you do not need to ensure the above hooks are triggered, and your component
|
/// If you do not need to ensure the above hooks are triggered, and your component
|
||||||
/// is mutable, prefer using [`get_mut`](DeferredWorld::get_mut).
|
/// is mutable, prefer using [`get_mut`](DeferredWorld::get_mut).
|
||||||
#[inline]
|
#[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,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
|
relationship_hook_mode: RelationshipHookMode,
|
||||||
f: impl FnOnce(&mut T) -> R,
|
f: impl FnOnce(&mut T) -> R,
|
||||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||||
// If the component is not registered, then it doesn't exist on this entity, so no action required.
|
// 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);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
self.modify_component_by_id(entity, component_id, move |component| {
|
self.modify_component_by_id_with_relationship_hook_mode(
|
||||||
// SAFETY: component matches the component_id collected in the above line
|
entity,
|
||||||
let mut component = unsafe { component.with_type::<T>() };
|
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
|
/// 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
|
/// 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).
|
/// 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.
|
/// whenever possible.
|
||||||
#[inline]
|
#[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,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
|
relationship_hook_mode: RelationshipHookMode,
|
||||||
f: impl for<'a> FnOnce(MutUntyped<'a>) -> R,
|
f: impl for<'a> FnOnce(MutUntyped<'a>) -> R,
|
||||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||||
let entity_cell = self.get_entity_mut(entity)?;
|
let entity_cell = self.get_entity_mut(entity)?;
|
||||||
@ -162,7 +171,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
[component_id].into_iter(),
|
[component_id].into_iter(),
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
RelationshipHookMode::Run,
|
relationship_hook_mode,
|
||||||
);
|
);
|
||||||
if archetype.has_replace_observer() {
|
if archetype.has_replace_observer() {
|
||||||
self.trigger_observers(
|
self.trigger_observers(
|
||||||
@ -202,7 +211,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
[component_id].into_iter(),
|
[component_id].into_iter(),
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
RelationshipHookMode::Run,
|
relationship_hook_mode,
|
||||||
);
|
);
|
||||||
if archetype.has_insert_observer() {
|
if archetype.has_insert_observer() {
|
||||||
self.trigger_observers(
|
self.trigger_observers(
|
||||||
|
|||||||
@ -219,6 +219,14 @@ impl World {
|
|||||||
&mut self.entities
|
&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.
|
/// Retrieves this world's [`Archetypes`] collection.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn archetypes(&self) -> &Archetypes {
|
pub fn archetypes(&self) -> &Archetypes {
|
||||||
@ -1426,6 +1434,7 @@ impl World {
|
|||||||
/// # assert_eq!(world.get::<Foo>(entity), Some(&Foo(true)));
|
/// # assert_eq!(world.get::<Foo>(entity), Some(&Foo(true)));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn modify_component<T: Component, R>(
|
pub fn modify_component<T: Component, R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
@ -1433,7 +1442,11 @@ impl World {
|
|||||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||||
let mut world = DeferredWorld::from(&mut *self);
|
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();
|
self.flush();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@ -1454,6 +1467,7 @@ impl World {
|
|||||||
/// You should prefer the typed [`modify_component`](World::modify_component)
|
/// You should prefer the typed [`modify_component`](World::modify_component)
|
||||||
/// whenever possible.
|
/// whenever possible.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[track_caller]
|
||||||
pub fn modify_component_by_id<R>(
|
pub fn modify_component_by_id<R>(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
@ -1462,7 +1476,12 @@ impl World {
|
|||||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||||
let mut world = DeferredWorld::from(&mut *self);
|
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();
|
self.flush();
|
||||||
Ok(result)
|
Ok(result)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_encase_derive"
|
name = "bevy_encase_derive"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Bevy derive macro for encase"
|
description = "Bevy derive macro for encase"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
|||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
encase_derive_impl = "0.10"
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_gilrs"
|
name = "bevy_gilrs"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Gamepad system made using Gilrs for Bevy Engine"
|
description = "Gamepad system made using Gilrs for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -10,12 +10,12 @@ keywords = ["bevy"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,7 @@ pub(crate) struct Gilrs {
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
cell: SyncCell<gilrs::Gilrs>,
|
cell: SyncCell<gilrs::Gilrs>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gilrs {
|
impl Gilrs {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with(&mut self, f: impl FnOnce(&mut gilrs::Gilrs)) {
|
pub fn with(&mut self, f: impl FnOnce(&mut gilrs::Gilrs)) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_gizmos"
|
name = "bevy_gizmos"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides gizmos for Bevy Engine"
|
description = "Provides gizmos for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -15,21 +15,21 @@ bevy_render = ["dep:bevy_render", "bevy_core_pipeline"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Bevy
|
# Bevy
|
||||||
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev", optional = true }
|
bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev", optional = true }
|
||||||
bevy_sprite = { path = "../bevy_sprite", version = "0.16.0-dev", optional = true }
|
bevy_sprite = { path = "../bevy_sprite", version = "0.17.0-dev", optional = true }
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev", optional = true }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev", optional = true }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev", optional = true }
|
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev", optional = true }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_gizmos_macros = { path = "macros", version = "0.16.0-dev" }
|
bevy_gizmos_macros = { path = "macros", version = "0.17.0-dev" }
|
||||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
bytemuck = "1.0"
|
bytemuck = "1.0"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_gizmos_macros"
|
name = "bevy_gizmos_macros"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Derive implementations for bevy_gizmos"
|
description = "Derive implementations for bevy_gizmos"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -13,7 +13,7 @@ proc-macro = true
|
|||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[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"
|
syn = "2.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|||||||
@ -172,6 +172,7 @@ where
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Config, Clear> GizmoBuffer<Config, Clear>
|
impl<Config, Clear> GizmoBuffer<Config, Clear>
|
||||||
where
|
where
|
||||||
Config: GizmoConfigGroup,
|
Config: GizmoConfigGroup,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_gltf"
|
name = "bevy_gltf"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Bevy Engine GLTF loading"
|
description = "Bevy Engine GLTF loading"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -18,25 +18,25 @@ pbr_specular_textures = ["bevy_pbr/pbr_specular_textures"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_animation = { path = "../bevy_animation", version = "0.16.0-dev", optional = true }
|
bevy_animation = { path = "../bevy_animation", version = "0.17.0-dev", optional = true }
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
|
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||||
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" }
|
bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", features = [
|
bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev", features = [
|
||||||
"bevy_render",
|
"bevy_render",
|
||||||
] }
|
] }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" }
|
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
"serialize",
|
"serialize",
|
||||||
] }
|
] }
|
||||||
@ -66,7 +66,7 @@ smallvec = "1.11"
|
|||||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_image"
|
name = "bevy_image"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides image types for Bevy Engine"
|
description = "Provides image types for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -40,17 +40,17 @@ zstd = ["ruzstd"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev", features = [
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [
|
||||||
"serialize",
|
"serialize",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
] }
|
] }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.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.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
|||||||
half = { version = "2.4.1" }
|
half = { version = "2.4.1" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -34,6 +34,7 @@ pub struct TextureAtlasSources {
|
|||||||
/// Maps from a specific image handle to the index in `textures` where they can be found.
|
/// Maps from a specific image handle to the index in `textures` where they can be found.
|
||||||
pub texture_ids: HashMap<AssetId<Image>, usize>,
|
pub texture_ids: HashMap<AssetId<Image>, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextureAtlasSources {
|
impl TextureAtlasSources {
|
||||||
/// Retrieves the texture *section* index of the given `texture` handle.
|
/// Retrieves the texture *section* index of the given `texture` handle.
|
||||||
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
|
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_input"
|
name = "bevy_input"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides input functionality for Bevy Engine"
|
description = "Provides input functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -60,14 +60,14 @@ libm = ["bevy_math/libm"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.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.16.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.16.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.16.0-dev", features = [
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||||
"glam",
|
"glam",
|
||||||
], default-features = false, optional = true }
|
], 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
|
# other
|
||||||
serde = { version = "1", features = [
|
serde = { version = "1", features = [
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_input_focus"
|
name = "bevy_input_focus"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Keyboard focus management"
|
description = "Keyboard focus management"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -60,12 +60,13 @@ libm = ["bevy_math/libm", "bevy_window/libm"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.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.16.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.16.0-dev", default-features = false }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev", default-features = false }
|
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
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",
|
"glam",
|
||||||
], default-features = false, optional = true }
|
], default-features = false, optional = true }
|
||||||
|
|
||||||
|
|||||||
@ -147,6 +147,15 @@ pub struct FocusedInput<E: BufferedEvent + Clone> {
|
|||||||
window: Entity,
|
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)]
|
#[derive(QueryData)]
|
||||||
/// These are for accessing components defined on the targeted entity
|
/// These are for accessing components defined on the targeted entity
|
||||||
pub struct WindowTraversal {
|
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.
|
/// 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,
|
/// To add bubbling to your own input events, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
|
||||||
|
|||||||
@ -38,11 +38,12 @@ use bevy_input::{
|
|||||||
keyboard::{KeyCode, KeyboardInput},
|
keyboard::{KeyCode, KeyboardInput},
|
||||||
ButtonInput, ButtonState,
|
ButtonInput, ButtonState,
|
||||||
};
|
};
|
||||||
use bevy_window::PrimaryWindow;
|
use bevy_picking::events::{Pointer, Press};
|
||||||
|
use bevy_window::{PrimaryWindow, Window};
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{FocusedInput, InputFocus, InputFocusVisible};
|
use crate::{AcquireFocus, FocusedInput, InputFocus, InputFocusVisible};
|
||||||
|
|
||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
use {
|
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.
|
/// Plugin for navigating between focusable entities using keyboard input.
|
||||||
pub struct TabNavigationPlugin;
|
pub struct TabNavigationPlugin;
|
||||||
|
|
||||||
@ -321,6 +347,8 @@ impl Plugin for TabNavigationPlugin {
|
|||||||
|
|
||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
app.register_type::<TabIndex>().register_type::<TabGroup>();
|
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.
|
/// Observer function which handles tab navigation.
|
||||||
///
|
///
|
||||||
/// This observer responds to [`KeyCode::Tab`] events and Shift+Tab events,
|
/// This observer responds to [`KeyCode::Tab`] events and Shift+Tab events,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_internal"
|
name = "bevy_internal"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
|
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -350,80 +350,80 @@ debug = ["bevy_utils/debug"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy (no_std)
|
# 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_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev", default-features = false }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev", default-features = false }
|
||||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.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.16.0-dev", default-features = false, features = [
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false, features = [
|
||||||
"bevy_reflect",
|
"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_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",
|
"bevy_reflect",
|
||||||
"nostd-libm",
|
"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",
|
"alloc",
|
||||||
] }
|
] }
|
||||||
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev", default-features = false }
|
bevy_ptr = { path = "../bevy_ptr", version = "0.17.0-dev", default-features = false }
|
||||||
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 = [
|
||||||
"smallvec",
|
"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_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-support",
|
||||||
"bevy_reflect",
|
"bevy_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_utils = { path = "../bevy_utils", 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.16.0-dev", default-features = false }
|
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||||
|
|
||||||
# bevy (std required)
|
# 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 (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_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.16.0-dev" }
|
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.16.0-dev" }
|
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
bevy_color = { path = "../bevy_color", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
"bevy_reflect",
|
"bevy_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.16.0-dev" }
|
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.16.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.16.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.16.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.16.0-dev" }
|
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.16.0-dev", default-features = false }
|
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.17.0-dev", default-features = false }
|
||||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.16.0-dev" }
|
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", optional = true, version = "0.16.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.16.0-dev", default-features = false, features = [
|
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||||
"bevy_reflect",
|
"bevy_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.16.0-dev" }
|
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.16.0-dev" }
|
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.16.0-dev" }
|
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.16.0-dev" }
|
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.16.0-dev" }
|
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.16.0-dev" }
|
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_state = { path = "../bevy_state", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
bevy_state = { path = "../bevy_state", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||||
"bevy_app",
|
"bevy_app",
|
||||||
"bevy_reflect",
|
"bevy_reflect",
|
||||||
] }
|
] }
|
||||||
bevy_text = { path = "../bevy_text", optional = true, version = "0.16.0-dev" }
|
bevy_text = { path = "../bevy_text", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.16.0-dev" }
|
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.17.0-dev" }
|
||||||
bevy_window = { path = "../bevy_window", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
bevy_window = { path = "../bevy_window", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||||
"bevy_reflect",
|
"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]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_log"
|
name = "bevy_log"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides logging for Bevy Engine"
|
description = "Provides logging for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -14,10 +14,10 @@ trace_tracy_memory = ["dep:tracy-client"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev" }
|
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
|
|
||||||
# other
|
# other
|
||||||
tracing-subscriber = { version = "0.3.1", features = [
|
tracing-subscriber = { version = "0.3.1", features = [
|
||||||
@ -40,7 +40,7 @@ android_log-sys = "0.3.0"
|
|||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
tracing-wasm = "0.2.1"
|
tracing-wasm = "0.2.1"
|
||||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||||
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",
|
"web",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_macro_utils"
|
name = "bevy_macro_utils"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "A collection of utils for Bevy Engine"
|
description = "A collection of utils for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_math"
|
name = "bevy_math"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides math functionality for Bevy Engine"
|
description = "Provides math functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
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 = { version = "0.8", default-features = false, optional = true }
|
||||||
rand_distr = { version = "0.4.3", optional = true }
|
rand_distr = { version = "0.4.3", optional = true }
|
||||||
smallvec = { version = "1.11" }
|
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",
|
"glam",
|
||||||
], optional = true }
|
], optional = true }
|
||||||
variadics_please = "1.1"
|
variadics_please = "1.1"
|
||||||
|
|||||||
@ -1347,6 +1347,7 @@ pub struct RationalSegment<P: VectorSpace> {
|
|||||||
/// The width of the domain of this segment.
|
/// The width of the domain of this segment.
|
||||||
pub knot_span: f32,
|
pub knot_span: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: VectorSpace<Scalar = f32>> RationalSegment<P> {
|
impl<P: VectorSpace<Scalar = f32>> RationalSegment<P> {
|
||||||
/// Instantaneous position of a point at parametric value `t` in `[0, 1]`.
|
/// Instantaneous position of a point at parametric value `t` in `[0, 1]`.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|||||||
@ -35,6 +35,7 @@ pub struct Circle {
|
|||||||
/// The radius of the circle
|
/// The radius of the circle
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Circle {}
|
impl Primitive2d for Circle {}
|
||||||
|
|
||||||
impl Default for Circle {
|
impl Default for Circle {
|
||||||
@ -124,6 +125,7 @@ pub struct Arc2d {
|
|||||||
/// Half the angle defining the arc
|
/// Half the angle defining the arc
|
||||||
pub half_angle: f32,
|
pub half_angle: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Arc2d {}
|
impl Primitive2d for Arc2d {}
|
||||||
|
|
||||||
impl Default for Arc2d {
|
impl Default for Arc2d {
|
||||||
@ -290,6 +292,7 @@ pub struct CircularSector {
|
|||||||
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
|
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
|
||||||
pub arc: Arc2d,
|
pub arc: Arc2d,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for CircularSector {}
|
impl Primitive2d for CircularSector {}
|
||||||
|
|
||||||
impl Default for CircularSector {
|
impl Default for CircularSector {
|
||||||
@ -433,6 +436,7 @@ pub struct CircularSegment {
|
|||||||
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
|
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
|
||||||
pub arc: Arc2d,
|
pub arc: Arc2d,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for CircularSegment {}
|
impl Primitive2d for CircularSegment {}
|
||||||
|
|
||||||
impl Default for CircularSegment {
|
impl Default for CircularSegment {
|
||||||
@ -453,6 +457,7 @@ impl Measured2d for CircularSegment {
|
|||||||
self.chord_length() + self.arc_length()
|
self.chord_length() + self.arc_length()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CircularSegment {
|
impl CircularSegment {
|
||||||
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
|
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -788,6 +793,7 @@ pub struct Ellipse {
|
|||||||
/// This corresponds to the two perpendicular radii defining the ellipse.
|
/// This corresponds to the two perpendicular radii defining the ellipse.
|
||||||
pub half_size: Vec2,
|
pub half_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Ellipse {}
|
impl Primitive2d for Ellipse {}
|
||||||
|
|
||||||
impl Default for Ellipse {
|
impl Default for Ellipse {
|
||||||
@ -939,6 +945,7 @@ pub struct Annulus {
|
|||||||
/// The outer circle of the annulus
|
/// The outer circle of the annulus
|
||||||
pub outer_circle: Circle,
|
pub outer_circle: Circle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Annulus {}
|
impl Primitive2d for Annulus {}
|
||||||
|
|
||||||
impl Default for Annulus {
|
impl Default for Annulus {
|
||||||
@ -1036,6 +1043,7 @@ pub struct Rhombus {
|
|||||||
/// Size of the horizontal and vertical diagonals of the rhombus
|
/// Size of the horizontal and vertical diagonals of the rhombus
|
||||||
pub half_diagonals: Vec2,
|
pub half_diagonals: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Rhombus {}
|
impl Primitive2d for Rhombus {}
|
||||||
|
|
||||||
impl Default 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
|
/// The normal of the plane. The plane will be placed perpendicular to this direction
|
||||||
pub normal: Dir2,
|
pub normal: Dir2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Plane2d {}
|
impl Primitive2d for Plane2d {}
|
||||||
|
|
||||||
impl Default for Plane2d {
|
impl Default for Plane2d {
|
||||||
@ -1213,6 +1222,7 @@ pub struct Line2d {
|
|||||||
/// and its opposite direction
|
/// and its opposite direction
|
||||||
pub direction: Dir2,
|
pub direction: Dir2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Line2d {}
|
impl Primitive2d for Line2d {}
|
||||||
|
|
||||||
/// A line segment defined by two endpoints in 2D space.
|
/// A line segment defined by two endpoints in 2D space.
|
||||||
@ -1232,6 +1242,7 @@ pub struct Segment2d {
|
|||||||
/// The endpoints of the line segment.
|
/// The endpoints of the line segment.
|
||||||
pub vertices: [Vec2; 2],
|
pub vertices: [Vec2; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Segment2d {}
|
impl Primitive2d for Segment2d {}
|
||||||
|
|
||||||
impl Segment2d {
|
impl Segment2d {
|
||||||
@ -1504,6 +1515,7 @@ pub struct Polyline2d<const N: usize> {
|
|||||||
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||||
pub vertices: [Vec2; N],
|
pub vertices: [Vec2; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Primitive2d for Polyline2d<N> {}
|
impl<const N: usize> Primitive2d for Polyline2d<N> {}
|
||||||
|
|
||||||
impl<const N: usize> FromIterator<Vec2> for Polyline2d<N> {
|
impl<const N: usize> FromIterator<Vec2> for Polyline2d<N> {
|
||||||
@ -1573,6 +1585,7 @@ pub struct Triangle2d {
|
|||||||
/// The vertices of the triangle
|
/// The vertices of the triangle
|
||||||
pub vertices: [Vec2; 3],
|
pub vertices: [Vec2; 3],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Triangle2d {}
|
impl Primitive2d for Triangle2d {}
|
||||||
|
|
||||||
impl Default for Triangle2d {
|
impl Default for Triangle2d {
|
||||||
@ -1745,6 +1758,7 @@ pub struct Rectangle {
|
|||||||
/// Half of the width and height of the rectangle
|
/// Half of the width and height of the rectangle
|
||||||
pub half_size: Vec2,
|
pub half_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Rectangle {}
|
impl Primitive2d for Rectangle {}
|
||||||
|
|
||||||
impl Default 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"))]
|
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||||
pub vertices: [Vec2; N],
|
pub vertices: [Vec2; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Primitive2d for Polygon<N> {}
|
impl<const N: usize> Primitive2d for Polygon<N> {}
|
||||||
|
|
||||||
impl<const N: usize> FromIterator<Vec2> 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"))]
|
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||||
vertices: [Vec2; N],
|
vertices: [Vec2; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Primitive2d for ConvexPolygon<N> {}
|
impl<const N: usize> Primitive2d for ConvexPolygon<N> {}
|
||||||
|
|
||||||
/// An error that happens when creating a [`ConvexPolygon`].
|
/// An error that happens when creating a [`ConvexPolygon`].
|
||||||
@ -2013,6 +2029,7 @@ pub struct RegularPolygon {
|
|||||||
/// The number of sides
|
/// The number of sides
|
||||||
pub sides: u32,
|
pub sides: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for RegularPolygon {}
|
impl Primitive2d for RegularPolygon {}
|
||||||
|
|
||||||
impl Default for RegularPolygon {
|
impl Default for RegularPolygon {
|
||||||
@ -2160,6 +2177,7 @@ pub struct Capsule2d {
|
|||||||
/// Half the height of the capsule, excluding the semicircles
|
/// Half the height of the capsule, excluding the semicircles
|
||||||
pub half_length: f32,
|
pub half_length: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive2d for Capsule2d {}
|
impl Primitive2d for Capsule2d {}
|
||||||
|
|
||||||
impl Default for Capsule2d {
|
impl Default for Capsule2d {
|
||||||
|
|||||||
@ -31,6 +31,7 @@ pub struct Sphere {
|
|||||||
/// The radius of the sphere
|
/// The radius of the sphere
|
||||||
pub radius: f32,
|
pub radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Sphere {}
|
impl Primitive3d for Sphere {}
|
||||||
|
|
||||||
impl Default for Sphere {
|
impl Default for Sphere {
|
||||||
@ -105,6 +106,7 @@ pub struct Plane3d {
|
|||||||
/// Half of the width and height of the plane
|
/// Half of the width and height of the plane
|
||||||
pub half_size: Vec2,
|
pub half_size: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Plane3d {}
|
impl Primitive3d for Plane3d {}
|
||||||
|
|
||||||
impl Default 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
|
/// The normal of the plane. The plane will be placed perpendicular to this direction
|
||||||
pub normal: Dir3,
|
pub normal: Dir3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for InfinitePlane3d {}
|
impl Primitive3d for InfinitePlane3d {}
|
||||||
|
|
||||||
impl Default for InfinitePlane3d {
|
impl Default for InfinitePlane3d {
|
||||||
@ -351,6 +354,7 @@ pub struct Line3d {
|
|||||||
/// The direction of the line
|
/// The direction of the line
|
||||||
pub direction: Dir3,
|
pub direction: Dir3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Line3d {}
|
impl Primitive3d for Line3d {}
|
||||||
|
|
||||||
/// A line segment defined by two endpoints in 3D space.
|
/// A line segment defined by two endpoints in 3D space.
|
||||||
@ -370,6 +374,7 @@ pub struct Segment3d {
|
|||||||
/// The endpoints of the line segment.
|
/// The endpoints of the line segment.
|
||||||
pub vertices: [Vec3; 2],
|
pub vertices: [Vec3; 2],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Segment3d {}
|
impl Primitive3d for Segment3d {}
|
||||||
|
|
||||||
impl Segment3d {
|
impl Segment3d {
|
||||||
@ -578,6 +583,7 @@ pub struct Polyline3d<const N: usize> {
|
|||||||
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||||
pub vertices: [Vec3; N],
|
pub vertices: [Vec3; N],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize> Primitive3d for Polyline3d<N> {}
|
impl<const N: usize> Primitive3d for Polyline3d<N> {}
|
||||||
|
|
||||||
impl<const N: usize> FromIterator<Vec3> 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
|
/// Half of the width, height and depth of the cuboid
|
||||||
pub half_size: Vec3,
|
pub half_size: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Cuboid {}
|
impl Primitive3d for Cuboid {}
|
||||||
|
|
||||||
impl Default for Cuboid {
|
impl Default for Cuboid {
|
||||||
@ -742,6 +749,7 @@ pub struct Cylinder {
|
|||||||
/// The half height of the cylinder
|
/// The half height of the cylinder
|
||||||
pub half_height: f32,
|
pub half_height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Cylinder {}
|
impl Primitive3d for Cylinder {}
|
||||||
|
|
||||||
impl Default for Cylinder {
|
impl Default for Cylinder {
|
||||||
@ -820,6 +828,7 @@ pub struct Capsule3d {
|
|||||||
/// Half the height of the capsule, excluding the hemispheres
|
/// Half the height of the capsule, excluding the hemispheres
|
||||||
pub half_length: f32,
|
pub half_length: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Capsule3d {}
|
impl Primitive3d for Capsule3d {}
|
||||||
|
|
||||||
impl Default for Capsule3d {
|
impl Default for Capsule3d {
|
||||||
@ -890,6 +899,7 @@ pub struct Cone {
|
|||||||
/// The height of the cone
|
/// The height of the cone
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Cone {}
|
impl Primitive3d for Cone {}
|
||||||
|
|
||||||
impl Default for Cone {
|
impl Default for Cone {
|
||||||
@ -974,6 +984,7 @@ pub struct ConicalFrustum {
|
|||||||
/// The height of the frustum
|
/// The height of the frustum
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for ConicalFrustum {}
|
impl Primitive3d for ConicalFrustum {}
|
||||||
|
|
||||||
impl Default for ConicalFrustum {
|
impl Default for ConicalFrustum {
|
||||||
@ -1030,6 +1041,7 @@ pub struct Torus {
|
|||||||
#[doc(alias = "radius_of_revolution")]
|
#[doc(alias = "radius_of_revolution")]
|
||||||
pub major_radius: f32,
|
pub major_radius: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Torus {}
|
impl Primitive3d for Torus {}
|
||||||
|
|
||||||
impl Default for Torus {
|
impl Default for Torus {
|
||||||
@ -1326,6 +1338,7 @@ pub struct Tetrahedron {
|
|||||||
/// The vertices of the tetrahedron.
|
/// The vertices of the tetrahedron.
|
||||||
pub vertices: [Vec3; 4],
|
pub vertices: [Vec3; 4],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive3d for Tetrahedron {}
|
impl Primitive3d for Tetrahedron {}
|
||||||
|
|
||||||
impl Default for Tetrahedron {
|
impl Default for Tetrahedron {
|
||||||
@ -1433,6 +1446,7 @@ pub struct Extrusion<T: Primitive2d> {
|
|||||||
/// Half of the depth of the extrusion
|
/// Half of the depth of the extrusion
|
||||||
pub half_depth: f32,
|
pub half_depth: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
|
impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
|
||||||
|
|
||||||
impl<T: Primitive2d> Extrusion<T> {
|
impl<T: Primitive2d> Extrusion<T> {
|
||||||
|
|||||||
@ -34,6 +34,7 @@ struct SweepLineEvent {
|
|||||||
/// Type of the vertex (left or right)
|
/// Type of the vertex (left or right)
|
||||||
endpoint: Endpoint,
|
endpoint: Endpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SweepLineEvent {
|
impl SweepLineEvent {
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
not(feature = "alloc"),
|
not(feature = "alloc"),
|
||||||
@ -46,17 +47,21 @@ impl SweepLineEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for SweepLineEvent {
|
impl PartialEq for SweepLineEvent {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.position() == other.position()
|
self.position() == other.position()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for SweepLineEvent {}
|
impl Eq for SweepLineEvent {}
|
||||||
|
|
||||||
impl PartialOrd for SweepLineEvent {
|
impl PartialOrd for SweepLineEvent {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for SweepLineEvent {
|
impl Ord for SweepLineEvent {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
xy_order(self.position(), other.position())
|
xy_order(self.position(), other.position())
|
||||||
@ -129,11 +134,13 @@ struct Segment {
|
|||||||
left: Vec2,
|
left: Vec2,
|
||||||
right: Vec2,
|
right: Vec2,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Segment {
|
impl PartialEq for Segment {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.edge_index == other.edge_index
|
self.edge_index == other.edge_index
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Segment {}
|
impl Eq for Segment {}
|
||||||
|
|
||||||
impl PartialOrd for Segment {
|
impl PartialOrd for Segment {
|
||||||
@ -141,6 +148,7 @@ impl PartialOrd for Segment {
|
|||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ord for Segment {
|
impl Ord for Segment {
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
self.left
|
self.left
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_mesh"
|
name = "bevy_mesh"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides mesh types for Bevy Engine"
|
description = "Provides mesh types for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -10,16 +10,16 @@ keywords = ["bevy"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.16.0-dev" }
|
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
"serialize",
|
"serialize",
|
||||||
] }
|
] }
|
||||||
|
|||||||
@ -163,6 +163,7 @@ impl Iterator for IndicesIter<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ExactSizeIterator for IndicesIter<'a> {}
|
impl<'a> ExactSizeIterator for IndicesIter<'a> {}
|
||||||
|
|
||||||
impl<'a> FusedIterator for IndicesIter<'a> {}
|
impl<'a> FusedIterator for IndicesIter<'a> {}
|
||||||
|
|
||||||
impl From<&Indices> for IndexFormat {
|
impl From<&Indices> for IndexFormat {
|
||||||
|
|||||||
@ -117,6 +117,7 @@ pub struct MorphWeights {
|
|||||||
/// The first mesh primitive assigned to these weights
|
/// The first mesh primitive assigned to these weights
|
||||||
first_mesh: Option<Handle<Mesh>>,
|
first_mesh: Option<Handle<Mesh>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MorphWeights {
|
impl MorphWeights {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
weights: Vec<f32>,
|
weights: Vec<f32>,
|
||||||
@ -160,6 +161,7 @@ impl MorphWeights {
|
|||||||
pub struct MeshMorphWeights {
|
pub struct MeshMorphWeights {
|
||||||
weights: Vec<f32>,
|
weights: Vec<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeshMorphWeights {
|
impl MeshMorphWeights {
|
||||||
pub fn new(weights: Vec<f32>) -> Result<Self, MorphBuildError> {
|
pub fn new(weights: Vec<f32>) -> Result<Self, MorphBuildError> {
|
||||||
if weights.len() > MAX_MORPH_WEIGHTS {
|
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.
|
/// animated, as the `w` component is the sign and cannot be animated.
|
||||||
pub tangent: Vec3,
|
pub tangent: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<[Vec3; 3]> for MorphAttributes {
|
impl From<[Vec3; 3]> for MorphAttributes {
|
||||||
fn from([position, normal, tangent]: [Vec3; 3]) -> Self {
|
fn from([position, normal, tangent]: [Vec3; 3]) -> Self {
|
||||||
MorphAttributes {
|
MorphAttributes {
|
||||||
@ -207,6 +210,7 @@ impl From<[Vec3; 3]> for MorphAttributes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MorphAttributes {
|
impl MorphAttributes {
|
||||||
/// How many components `MorphAttributes` has.
|
/// How many components `MorphAttributes` has.
|
||||||
///
|
///
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_mikktspace"
|
name = "bevy_mikktspace"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
authors = [
|
authors = [
|
||||||
"Benjamin Wasty <benny.wasty@gmail.com>",
|
"Benjamin Wasty <benny.wasty@gmail.com>",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_pbr"
|
name = "bevy_pbr"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Adds PBR rendering to Bevy Engine"
|
description = "Adds PBR rendering to Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -31,22 +31,22 @@ meshlet_processor = [
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", optional = true }
|
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
@ -535,12 +535,12 @@ pub fn extract_clusters(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_entities: usize = clusters
|
let entity_count: usize = clusters
|
||||||
.clusterable_objects
|
.clusterable_objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|l| l.entities.len())
|
.map(|l| l.entities.len())
|
||||||
.sum();
|
.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 {
|
for cluster_objects in &clusters.clusterable_objects {
|
||||||
data.push(ExtractedClusterableObjectElement::ClusterHeader(
|
data.push(ExtractedClusterableObjectElement::ClusterHeader(
|
||||||
cluster_objects.counts,
|
cluster_objects.counts,
|
||||||
|
|||||||
@ -48,6 +48,7 @@ impl Default for AmbientLight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AmbientLight {
|
impl AmbientLight {
|
||||||
pub const NONE: AmbientLight = AmbientLight {
|
pub const NONE: AmbientLight = AmbientLight {
|
||||||
color: Color::WHITE,
|
color: Color::WHITE,
|
||||||
|
|||||||
@ -33,6 +33,7 @@ pub enum ParallaxMappingMethod {
|
|||||||
max_steps: u32,
|
max_steps: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ParallaxMappingMethod {
|
impl ParallaxMappingMethod {
|
||||||
/// [`ParallaxMappingMethod::Relief`] with a 5 steps, a reasonable default.
|
/// [`ParallaxMappingMethod::Relief`] with a 5 steps, a reasonable default.
|
||||||
pub const DEFAULT_RELIEF_MAPPING: Self = ParallaxMappingMethod::Relief { max_steps: 5 };
|
pub const DEFAULT_RELIEF_MAPPING: Self = ParallaxMappingMethod::Relief { max_steps: 5 };
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "bevy_picking"
|
name = "bevy_picking"
|
||||||
version = "0.16.0-dev"
|
version = "0.17.0-dev"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Provides screen picking functionality for Bevy Engine"
|
description = "Provides screen picking functionality for Bevy Engine"
|
||||||
homepage = "https://bevy.org"
|
homepage = "https://bevy.org"
|
||||||
@ -13,20 +13,20 @@ bevy_mesh_picking_backend = ["dep:bevy_mesh", "dep:crossbeam-channel"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev", optional = true }
|
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev", optional = true }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||||
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 = [
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user