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 errors \
|
||||
--exclude bevy_mobile_example \
|
||||
--exclude build-wasm-example
|
||||
--exclude build-wasm-example \
|
||||
--exclude no_std_library
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
|
||||
27
Cargo.toml
27
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||
description = "A refreshingly simple data-driven game engine and app framework"
|
||||
@ -72,7 +72,6 @@ allow_attributes_without_reason = "warn"
|
||||
|
||||
[workspace.lints.rust]
|
||||
missing_docs = "warn"
|
||||
mismatched_lifetime_syntaxes = "allow"
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
||||
unsafe_code = "deny"
|
||||
unsafe_op_in_unsafe_fn = "warn"
|
||||
@ -559,12 +558,12 @@ hotpatching = ["bevy_internal/hotpatching"]
|
||||
debug = ["bevy_internal/debug"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.17.0-dev", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
|
||||
# Wasm does not support dynamic linking.
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.0"
|
||||
@ -574,14 +573,14 @@ flate2 = "1.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
bytemuck = "1.7"
|
||||
bevy_render = { path = "crates/bevy_render", version = "0.16.0-dev", default-features = false }
|
||||
bevy_render = { path = "crates/bevy_render", version = "0.17.0-dev", default-features = false }
|
||||
# The following explicit dependencies are needed for proc macros to work inside of examples as they are part of the bevy crate itself.
|
||||
bevy_ecs = { path = "crates/bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_state = { path = "crates/bevy_state", version = "0.16.0-dev", default-features = false }
|
||||
bevy_asset = { path = "crates/bevy_asset", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "crates/bevy_reflect", version = "0.16.0-dev", default-features = false }
|
||||
bevy_image = { path = "crates/bevy_image", version = "0.16.0-dev", default-features = false }
|
||||
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "crates/bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_state = { path = "crates/bevy_state", version = "0.17.0-dev", default-features = false }
|
||||
bevy_asset = { path = "crates/bevy_asset", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "crates/bevy_reflect", version = "0.17.0-dev", default-features = false }
|
||||
bevy_image = { path = "crates/bevy_image", version = "0.17.0-dev", default-features = false }
|
||||
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.17.0-dev", default-features = false }
|
||||
# Needed to poll Task examples
|
||||
futures-lite = "2.0.1"
|
||||
async-std = "1.13"
|
||||
@ -608,7 +607,7 @@ web-sys = { version = "0.3", features = ["Window"] }
|
||||
|
||||
[[example]]
|
||||
name = "context_menu"
|
||||
path = "examples/usages/context_menu.rs"
|
||||
path = "examples/usage/context_menu.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.context_menu]
|
||||
@ -1285,9 +1284,9 @@ required-features = ["bevy_solari"]
|
||||
|
||||
[package.metadata.example.solari]
|
||||
name = "Solari"
|
||||
description = "Demonstrates realtime dynamic global illumination rendering using Bevy Solari."
|
||||
description = "Demonstrates realtime dynamic raytraced lighting using Bevy Solari."
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
wasm = false # Raytracing is not supported on the web
|
||||
|
||||
[[example]]
|
||||
name = "spherical_area_lights"
|
||||
|
||||
@ -49,6 +49,7 @@ impl BenchModify for Table {
|
||||
black_box(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BenchModify for Sparse {
|
||||
fn bench_modify(&mut self) -> f32 {
|
||||
self.0 += 1f32;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_a11y"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides accessibility support for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -40,10 +40,10 @@ critical-section = [
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
|
||||
# other
|
||||
accesskit = { version = "0.19", default-features = false }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_animation"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides animation functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -10,22 +10,22 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
"petgraph",
|
||||
] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_anti_aliasing"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides various anti aliasing implementations for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -16,17 +16,17 @@ smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_app"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides core App functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -79,12 +79,12 @@ hotpatching = [
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false }
|
||||
|
||||
# other
|
||||
downcast-rs = { version = "2", default-features = false }
|
||||
|
||||
@ -1582,7 +1582,7 @@ mod tests {
|
||||
app.add_systems(EnterMainMenu, (foo, bar));
|
||||
|
||||
app.world_mut().run_schedule(EnterMainMenu);
|
||||
assert_eq!(app.world().entities().count_constructed(), 2);
|
||||
assert_eq!(app.world().entity_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -88,6 +88,7 @@ impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
|
||||
}
|
||||
|
||||
impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
|
||||
|
||||
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self._p.hash(state);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_asset"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides asset functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -19,19 +19,19 @@ watch = []
|
||||
trace = []
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_asset_macros = { path = "macros", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_asset_macros = { path = "macros", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"uuid",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [
|
||||
"async_executor",
|
||||
] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
@ -65,7 +65,7 @@ uuid = { version = "1.13.1", default-features = false, features = [
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
@ -78,13 +78,13 @@ web-sys = { version = "0.3", features = [
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_asset_macros"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Derive implementations for bevy_asset"
|
||||
homepage = "https://bevy.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
||||
@ -20,6 +20,7 @@ pub trait DirectAssetAccessExt {
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
) -> Handle<A>;
|
||||
}
|
||||
|
||||
impl DirectAssetAccessExt for World {
|
||||
/// Insert an asset similarly to [`Assets::add`].
|
||||
///
|
||||
|
||||
@ -56,6 +56,7 @@ pub(crate) struct EmbeddedEventHandler {
|
||||
dir: Dir,
|
||||
last_event: Option<AssetSourceEvent>,
|
||||
}
|
||||
|
||||
impl FilesystemEventHandler for EmbeddedEventHandler {
|
||||
fn begin(&mut self) {
|
||||
self.last_event = None;
|
||||
|
||||
@ -141,16 +141,19 @@ impl EmbeddedAssetRegistry {
|
||||
pub trait GetAssetServer {
|
||||
fn get_asset_server(&self) -> &AssetServer;
|
||||
}
|
||||
|
||||
impl GetAssetServer for App {
|
||||
fn get_asset_server(&self) -> &AssetServer {
|
||||
self.world().get_asset_server()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAssetServer for World {
|
||||
fn get_asset_server(&self) -> &AssetServer {
|
||||
self.resource()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAssetServer for AssetServer {
|
||||
fn get_asset_server(&self) -> &AssetServer {
|
||||
self
|
||||
|
||||
@ -223,6 +223,7 @@ pub struct ReflectHandle {
|
||||
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
|
||||
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
|
||||
}
|
||||
|
||||
impl ReflectHandle {
|
||||
/// The [`TypeId`] of the asset
|
||||
pub fn asset_type_id(&self) -> TypeId {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_audio"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides audio functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -10,13 +10,13 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
|
||||
@ -36,10 +36,10 @@ coreaudio-sys = { version = "0.2.17", default-features = false }
|
||||
rodio = { version = "0.20", default-features = false, features = [
|
||||
"wasm-bindgen",
|
||||
] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
|
||||
@ -57,6 +57,7 @@ pub struct PlaybackRemoveMarker;
|
||||
pub(crate) struct EarPositions<'w, 's> {
|
||||
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
|
||||
}
|
||||
|
||||
impl<'w, 's> EarPositions<'w, 's> {
|
||||
/// Gets a set of transformed ear positions.
|
||||
///
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_color"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Types for representing and manipulating color values"
|
||||
homepage = "https://bevy.org"
|
||||
@ -10,10 +10,10 @@ keywords = ["bevy", "color"]
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dependencies]
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false, features = [
|
||||
"curve",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
serde = { version = "1.0", features = [
|
||||
"derive",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_core_pipeline"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
authors = [
|
||||
"Bevy Contributors <bevyengine@gmail.com>",
|
||||
@ -20,20 +20,20 @@ tonemapping_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
@ -80,6 +80,7 @@ impl From<TextureUsages> for Camera3dDepthTextureUsage {
|
||||
Self(value.bits())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Camera3dDepthTextureUsage> for TextureUsages {
|
||||
fn from(value: Camera3dDepthTextureUsage) -> Self {
|
||||
Self::from_bits_truncate(value.0)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_core_widgets"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Unstyled common widgets for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,17 +10,17 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
||||
bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||
bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
|
||||
"bevy_ui_picking_backend",
|
||||
] }
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ use accesskit::Role;
|
||||
use bevy_a11y::AccessibilityNode;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::query::Has;
|
||||
use bevy_ecs::system::ResMut;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
@ -11,7 +10,8 @@ use bevy_ecs::{
|
||||
system::{Commands, Query, SystemId},
|
||||
};
|
||||
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
||||
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
|
||||
use bevy_input::ButtonState;
|
||||
use bevy_input_focus::FocusedInput;
|
||||
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
|
||||
use bevy_ui::{InteractionDisabled, Pressed};
|
||||
|
||||
@ -36,6 +36,7 @@ fn button_on_key_event(
|
||||
if !disabled {
|
||||
let event = &trigger.event().input;
|
||||
if !event.repeat
|
||||
&& event.state == ButtonState::Pressed
|
||||
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
|
||||
{
|
||||
if let Some(on_click) = bstate.on_click {
|
||||
@ -65,25 +66,13 @@ fn button_on_pointer_click(
|
||||
fn button_on_pointer_down(
|
||||
mut trigger: On<Pointer<Press>>,
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
focus: Option<ResMut<InputFocus>>,
|
||||
focus_visible: Option<ResMut<InputFocusVisible>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled {
|
||||
if !pressed {
|
||||
if !disabled && !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 bevy_a11y::AccessibilityNode;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::entity::Entity;
|
||||
use bevy_ecs::event::{EntityEvent, Event};
|
||||
use bevy_ecs::hierarchy::{ChildOf, Children};
|
||||
use bevy_ecs::hierarchy::Children;
|
||||
use bevy_ecs::lifecycle::Insert;
|
||||
use bevy_ecs::query::Has;
|
||||
use bevy_ecs::system::{In, Res, ResMut};
|
||||
use bevy_ecs::system::{In, Res};
|
||||
use bevy_ecs::world::DeferredWorld;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
@ -18,7 +17,7 @@ use bevy_ecs::{
|
||||
};
|
||||
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
||||
use bevy_input::ButtonState;
|
||||
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
|
||||
use bevy_input_focus::FocusedInput;
|
||||
use bevy_log::warn_once;
|
||||
use bevy_picking::events::{Drag, DragEnd, DragStart, Pointer, Press};
|
||||
use bevy_ui::{ComputedNode, ComputedNodeTarget, InteractionDisabled, UiGlobalTransform, UiScale};
|
||||
@ -207,43 +206,18 @@ pub(crate) fn slider_on_pointer_down(
|
||||
)>,
|
||||
q_thumb: Query<&ComputedNode, With<CoreSliderThumb>>,
|
||||
q_children: Query<&Children>,
|
||||
q_parents: Query<&ChildOf>,
|
||||
focus: Option<ResMut<InputFocus>>,
|
||||
focus_visible: Option<ResMut<InputFocusVisible>>,
|
||||
mut commands: Commands,
|
||||
ui_scale: Res<UiScale>,
|
||||
) {
|
||||
if q_thumb.contains(trigger.target()) {
|
||||
// Thumb click, stop propagation to prevent track click.
|
||||
trigger.propagate(false);
|
||||
|
||||
// Find the slider entity that's an ancestor of the thumb
|
||||
if let Some(slider_entity) = q_parents
|
||||
.iter_ancestors(trigger.target())
|
||||
.find(|entity| q_slider.contains(*entity))
|
||||
{
|
||||
// Set focus to slider and hide focus ring
|
||||
if let Some(mut focus) = focus {
|
||||
focus.0 = Some(slider_entity);
|
||||
}
|
||||
if let Some(mut focus_visible) = focus_visible {
|
||||
focus_visible.0 = false;
|
||||
}
|
||||
}
|
||||
} else if let Ok((slider, value, range, step, node, node_target, transform, disabled)) =
|
||||
q_slider.get(trigger.target())
|
||||
{
|
||||
// Track click
|
||||
trigger.propagate(false);
|
||||
|
||||
// Set focus to slider and hide focus ring
|
||||
if let Some(mut focus) = focus {
|
||||
focus.0 = (trigger.target() != Entity::PLACEHOLDER).then_some(trigger.target());
|
||||
}
|
||||
if let Some(mut focus_visible) = focus_visible {
|
||||
focus_visible.0 = false;
|
||||
}
|
||||
|
||||
if disabled {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -15,11 +15,13 @@
|
||||
// the widget level, like `SliderValue`, should not have the `Core` prefix.
|
||||
|
||||
mod core_button;
|
||||
mod core_checkbox;
|
||||
mod core_slider;
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
|
||||
pub use core_button::{CoreButton, CoreButtonPlugin};
|
||||
pub use core_checkbox::{CoreCheckbox, CoreCheckboxPlugin, SetChecked, ToggleChecked};
|
||||
pub use core_slider::{
|
||||
CoreSlider, CoreSliderDragState, CoreSliderPlugin, CoreSliderThumb, SetSliderValue,
|
||||
SliderRange, SliderStep, SliderValue, TrackClick,
|
||||
@ -31,6 +33,6 @@ pub struct CoreWidgetsPlugin;
|
||||
|
||||
impl Plugin for CoreWidgetsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((CoreButtonPlugin, CoreSliderPlugin));
|
||||
app.add_plugins((CoreButtonPlugin, CoreCheckboxPlugin, CoreSliderPlugin));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_derive"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides derive implementations for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["full"] }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_dev_tools"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Collection of developer tools for the Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -13,21 +13,21 @@ bevy_ci_testing = ["serde", "ron"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.16.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_state = { path = "../bevy_state", version = "0.16.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_state = { path = "../bevy_state", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_diagnostic"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides diagnostic functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -53,12 +53,12 @@ critical-section = [
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
|
||||
|
||||
@ -17,11 +17,13 @@ pub struct FrameTimeDiagnosticsPlugin {
|
||||
/// The smoothing factor for the exponential moving average. Usually `2.0 / (history_length + 1.0)`.
|
||||
pub smoothing_factor: f64,
|
||||
}
|
||||
|
||||
impl Default for FrameTimeDiagnosticsPlugin {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_MAX_HISTORY_LENGTH)
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameTimeDiagnosticsPlugin {
|
||||
/// Creates a new `FrameTimeDiagnosticsPlugin` with the specified `max_history_length` and a
|
||||
/// reasonable `smoothing_factor`.
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_dylib"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Force the Bevy Engine to be dynamically linked for faster linking"
|
||||
homepage = "https://bevy.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
crate-type = ["dylib"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "../bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||
bevy_internal = { path = "../bevy_internal", version = "0.17.0-dev", default-features = false }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_ecs"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Bevy Engine's entity component system"
|
||||
homepage = "https://bevy.org"
|
||||
@ -86,14 +86,14 @@ critical-section = [
|
||||
hotpatching = ["dep:subsecond"]
|
||||
|
||||
[dependencies]
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
"smallvec",
|
||||
], default-features = false, optional = true }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs_macros = { path = "macros", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs_macros = { path = "macros", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_ecs_macros"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
description = "Bevy ECS Macros"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
|
||||
syn = { version = "2.0.99", features = ["full", "extra-traits"] }
|
||||
quote = "1.0"
|
||||
|
||||
@ -792,6 +792,11 @@ fn derive_relationship(
|
||||
#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 proc_macro::TokenStream;
|
||||
use proc_macro2::{Ident, Span};
|
||||
use quote::{format_ident, quote};
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
use syn::{
|
||||
parse_macro_input, parse_quote, punctuated::Punctuated, spanned::Spanned, token::Comma,
|
||||
ConstParam, Data, DataStruct, DeriveInput, GenericParam, Index, TypeParam,
|
||||
@ -79,6 +79,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
let mut field_kind = Vec::with_capacity(named_fields.len());
|
||||
|
||||
for field in named_fields {
|
||||
let mut kind = BundleFieldKind::Component;
|
||||
|
||||
for attr in field
|
||||
.attrs
|
||||
.iter()
|
||||
@ -86,7 +88,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
{
|
||||
if let Err(error) = attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
|
||||
field_kind.push(BundleFieldKind::Ignore);
|
||||
kind = BundleFieldKind::Ignore;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(meta.error(format!(
|
||||
@ -98,7 +100,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
field_kind.push(BundleFieldKind::Component);
|
||||
field_kind.push(kind);
|
||||
}
|
||||
|
||||
let field = named_fields
|
||||
@ -111,82 +113,33 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
.map(|field| &field.ty)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut field_component_ids = Vec::new();
|
||||
let mut field_get_component_ids = Vec::new();
|
||||
let mut field_get_components = Vec::new();
|
||||
let mut field_from_components = Vec::new();
|
||||
let mut field_required_components = Vec::new();
|
||||
let mut active_field_types = Vec::new();
|
||||
let mut active_field_tokens = Vec::new();
|
||||
let mut inactive_field_tokens = Vec::new();
|
||||
for (((i, field_type), field_kind), field) in field_type
|
||||
.iter()
|
||||
.enumerate()
|
||||
.zip(field_kind.iter())
|
||||
.zip(field.iter())
|
||||
{
|
||||
let field_tokens = match field {
|
||||
Some(field) => field.to_token_stream(),
|
||||
None => Index::from(i).to_token_stream(),
|
||||
};
|
||||
match field_kind {
|
||||
BundleFieldKind::Component => {
|
||||
field_component_ids.push(quote! {
|
||||
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, &mut *ids);
|
||||
});
|
||||
field_required_components.push(quote! {
|
||||
<#field_type as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);
|
||||
});
|
||||
field_get_component_ids.push(quote! {
|
||||
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
|
||||
});
|
||||
match field {
|
||||
Some(field) => {
|
||||
field_get_components.push(quote! {
|
||||
self.#field.get_components(&mut *func);
|
||||
});
|
||||
field_from_components.push(quote! {
|
||||
#field: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
let index = Index::from(i);
|
||||
field_get_components.push(quote! {
|
||||
self.#index.get_components(&mut *func);
|
||||
});
|
||||
field_from_components.push(quote! {
|
||||
#index: <#field_type as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),
|
||||
});
|
||||
}
|
||||
}
|
||||
active_field_types.push(field_type);
|
||||
active_field_tokens.push(field_tokens);
|
||||
}
|
||||
|
||||
BundleFieldKind::Ignore => {
|
||||
field_from_components.push(quote! {
|
||||
#field: ::core::default::Default::default(),
|
||||
});
|
||||
}
|
||||
BundleFieldKind::Ignore => inactive_field_tokens.push(field_tokens),
|
||||
}
|
||||
}
|
||||
let generics = ast.generics;
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let struct_name = &ast.ident;
|
||||
|
||||
let from_components = attributes.impl_from_components.then(|| quote! {
|
||||
// SAFETY:
|
||||
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
|
||||
#[allow(deprecated)]
|
||||
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
|
||||
#[allow(unused_variables, non_snake_case)]
|
||||
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
|
||||
where
|
||||
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
|
||||
{
|
||||
Self{
|
||||
#(#field_from_components)*
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let attribute_errors = &errors;
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#(#attribute_errors)*
|
||||
|
||||
let bundle_impl = quote! {
|
||||
// SAFETY:
|
||||
// - ComponentId is returned in field-definition-order. [get_components] uses field-definition-order
|
||||
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
|
||||
@ -196,27 +149,27 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
fn component_ids(
|
||||
components: &mut #ecs_path::component::ComponentsRegistrator,
|
||||
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
|
||||
){
|
||||
#(#field_component_ids)*
|
||||
) {
|
||||
#(<#active_field_types as #ecs_path::bundle::Bundle>::component_ids(components, ids);)*
|
||||
}
|
||||
|
||||
fn get_component_ids(
|
||||
components: &#ecs_path::component::Components,
|
||||
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
|
||||
){
|
||||
#(#field_get_component_ids)*
|
||||
) {
|
||||
#(<#active_field_types as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);)*
|
||||
}
|
||||
|
||||
fn register_required_components(
|
||||
components: &mut #ecs_path::component::ComponentsRegistrator,
|
||||
required_components: &mut #ecs_path::component::RequiredComponents
|
||||
){
|
||||
#(#field_required_components)*
|
||||
) {
|
||||
#(<#active_field_types as #ecs_path::bundle::Bundle>::register_required_components(components, required_components);)*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#from_components
|
||||
|
||||
let dynamic_bundle_impl = quote! {
|
||||
#[allow(deprecated)]
|
||||
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
||||
type Effect = ();
|
||||
@ -226,9 +179,36 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
self,
|
||||
func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
|
||||
) {
|
||||
#(#field_get_components)*
|
||||
#(<#active_field_types as #ecs_path::bundle::DynamicBundle>::get_components(self.#active_field_tokens, &mut *func);)*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let from_components_impl = attributes.impl_from_components.then(|| quote! {
|
||||
// SAFETY:
|
||||
// - ComponentId is returned in field-definition-order. [from_components] uses field-definition-order
|
||||
#[allow(deprecated)]
|
||||
unsafe impl #impl_generics #ecs_path::bundle::BundleFromComponents for #struct_name #ty_generics #where_clause {
|
||||
#[allow(unused_variables, non_snake_case)]
|
||||
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
|
||||
where
|
||||
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
|
||||
{
|
||||
Self {
|
||||
#(#active_field_tokens: <#active_field_types as #ecs_path::bundle::BundleFromComponents>::from_components(ctx, &mut *func),)*
|
||||
#(#inactive_field_tokens: ::core::default::Default::default(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let attribute_errors = &errors;
|
||||
|
||||
TokenStream::from(quote! {
|
||||
#(#attribute_errors)*
|
||||
#bundle_impl
|
||||
#from_components_impl
|
||||
#dynamic_bundle_impl
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -83,7 +83,7 @@ pub fn derive_query_data_impl(input: TokenStream) -> TokenStream {
|
||||
let user_generics_with_world_and_state = {
|
||||
let mut generics = ast.generics;
|
||||
generics.params.insert(0, parse_quote!('__w));
|
||||
generics.params.insert(0, parse_quote!('__s));
|
||||
generics.params.insert(1, parse_quote!('__s));
|
||||
generics
|
||||
};
|
||||
let (
|
||||
|
||||
@ -960,6 +960,7 @@ impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {
|
||||
&self.archetypes[index.start.0.index()..]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ArchetypeId> for Archetypes {
|
||||
type Output = Archetype;
|
||||
|
||||
|
||||
@ -2410,4 +2410,13 @@ mod tests {
|
||||
|
||||
assert_eq!(world.resource::<Count>().0, 3);
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
#[expect(unused, reason = "tests the output of the derive macro is valid")]
|
||||
struct Ignore {
|
||||
#[bundle(ignore)]
|
||||
foo: i32,
|
||||
#[bundle(ignore)]
|
||||
bar: i32,
|
||||
}
|
||||
}
|
||||
|
||||
@ -623,6 +623,7 @@ pub trait ComponentMutability: private::Seal + 'static {
|
||||
pub struct Immutable;
|
||||
|
||||
impl private::Seal for Immutable {}
|
||||
|
||||
impl ComponentMutability for Immutable {
|
||||
const MUTABLE: bool = false;
|
||||
}
|
||||
@ -633,6 +634,7 @@ impl ComponentMutability for Immutable {
|
||||
pub struct Mutable;
|
||||
|
||||
impl private::Seal for Mutable {}
|
||||
|
||||
impl ComponentMutability for Mutable {
|
||||
const MUTABLE: bool = true;
|
||||
}
|
||||
@ -2968,6 +2970,7 @@ impl<T> Default for DefaultCloneBehaviorSpecialization<T> {
|
||||
pub trait DefaultCloneBehaviorBase {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Default
|
||||
@ -2979,6 +2982,7 @@ impl<C> DefaultCloneBehaviorBase for DefaultCloneBehaviorSpecialization<C> {
|
||||
pub trait DefaultCloneBehaviorViaClone {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
|
||||
}
|
||||
|
||||
impl<C: Clone + Component> DefaultCloneBehaviorViaClone for &DefaultCloneBehaviorSpecialization<C> {
|
||||
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::clone::<C>()
|
||||
|
||||
@ -866,6 +866,7 @@ impl<'a> Iterator for AllocEntitiesIterator<'a> {
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for AllocEntitiesIterator<'a> {}
|
||||
|
||||
impl<'a> core::iter::FusedIterator for AllocEntitiesIterator<'a> {}
|
||||
|
||||
// SAFETY: Newly allocated entity values are unique.
|
||||
|
||||
@ -154,6 +154,7 @@ impl<T: EntityEquivalent, const N: usize> DerefMut for UniqueEntityEquivalentArr
|
||||
unsafe { UniqueEntityEquivalentSlice::from_slice_unchecked_mut(&mut self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: EntityEquivalent> Default for UniqueEntityEquivalentArray<T, 0> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
@ -527,6 +528,7 @@ impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
||||
self.eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
||||
PartialEq<&UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
|
||||
{
|
||||
@ -550,6 +552,7 @@ impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
||||
self.eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq<U>, U: EntityEquivalent, const N: usize>
|
||||
PartialEq<UniqueEntityEquivalentArray<U, N>> for VecDeque<T>
|
||||
{
|
||||
|
||||
@ -1592,9 +1592,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Attempted to access or drop non-send resource bevy_ecs::tests::NonSendA from thread"
|
||||
)]
|
||||
#[should_panic]
|
||||
fn non_send_resource_drop_from_different_thread() {
|
||||
let mut world = World::default();
|
||||
world.insert_non_send_resource(NonSendA::default());
|
||||
@ -1659,7 +1657,7 @@ mod tests {
|
||||
|
||||
assert_eq!(q1.iter(&world).len(), 1);
|
||||
assert_eq!(q2.iter(&world).len(), 1);
|
||||
assert_eq!(world.entities().count_constructed(), 2);
|
||||
assert_eq!(world.entity_count(), 2);
|
||||
|
||||
world.clear_entities();
|
||||
|
||||
@ -1674,7 +1672,7 @@ mod tests {
|
||||
"world should not contain sparse set components"
|
||||
);
|
||||
assert_eq!(
|
||||
world.entities().count_constructed(),
|
||||
world.entity_count(),
|
||||
0,
|
||||
"world should not have any entities"
|
||||
);
|
||||
@ -2609,7 +2607,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Recursive required components detected: A → B → C → B\nhelp: If this is intentional, consider merging the components."]
|
||||
#[should_panic]
|
||||
fn required_components_recursion_errors() {
|
||||
#[derive(Component, Default)]
|
||||
#[require(B)]
|
||||
@ -2627,7 +2625,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Recursive required components detected: A → A\nhelp: Remove require(A)."]
|
||||
#[should_panic]
|
||||
fn required_components_self_errors() {
|
||||
#[derive(Component, Default)]
|
||||
#[require(A)]
|
||||
|
||||
@ -159,6 +159,7 @@ impl From<&str> for Name {
|
||||
Name::new(name.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Name {
|
||||
#[inline(always)]
|
||||
fn from(name: String) -> Self {
|
||||
@ -174,12 +175,14 @@ impl AsRef<str> for Name {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Name> for String {
|
||||
#[inline(always)]
|
||||
fn from(val: &Name) -> String {
|
||||
val.as_str().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Name> for String {
|
||||
#[inline(always)]
|
||||
fn from(val: Name) -> String {
|
||||
|
||||
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::{
|
||||
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
|
||||
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
||||
lifecycle::{ComponentHook, HookContext},
|
||||
component::ComponentCloneBehavior,
|
||||
entity::{ComponentCloneCtx, EntityClonerBuilder, EntityMapper, SourceComponent},
|
||||
observer::ObservedBy,
|
||||
world::World,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use crate::prelude::ReflectComponent;
|
||||
|
||||
use super::Observer;
|
||||
|
||||
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
|
||||
#[derive(Default, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Component, Debug))]
|
||||
pub struct ObservedBy(pub(crate) Vec<Entity>);
|
||||
|
||||
impl ObservedBy {
|
||||
/// Provides a read-only reference to the list of entities observing this entity.
|
||||
pub fn get(&self) -> &[Entity] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ObservedBy {
|
||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||
type Mutability = Mutable;
|
||||
|
||||
fn on_remove() -> Option<ComponentHook> {
|
||||
Some(|mut world, HookContext { entity, .. }| {
|
||||
let observed_by = {
|
||||
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
|
||||
core::mem::take(&mut component.0)
|
||||
};
|
||||
for e in observed_by {
|
||||
let (total_entities, despawned_watched_entities) = {
|
||||
let Ok(mut entity_mut) = world.get_entity_mut(e) else {
|
||||
continue;
|
||||
};
|
||||
let Some(mut state) = entity_mut.get_mut::<Observer>() else {
|
||||
continue;
|
||||
};
|
||||
state.despawned_watched_entities += 1;
|
||||
(
|
||||
state.descriptor.entities.len(),
|
||||
state.despawned_watched_entities as usize,
|
||||
)
|
||||
};
|
||||
|
||||
// Despawn Observer if it has no more active sources.
|
||||
if total_entities == despawned_watched_entities {
|
||||
world.commands().entity(e).despawn();
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn clone_behavior() -> ComponentCloneBehavior {
|
||||
ComponentCloneBehavior::Ignore
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityClonerBuilder<'_> {
|
||||
/// Sets the option to automatically add cloned entities to the observers targeting source entity.
|
||||
pub fn add_observers(&mut self, add_observers: bool) -> &mut Self {
|
||||
@ -129,614 +129,26 @@
|
||||
//! but allows for a more ad hoc approach with observers,
|
||||
//! and enables indefinite chaining of observers triggering other observers (for both better and worse!).
|
||||
|
||||
mod entity_observer;
|
||||
mod centralized_storage;
|
||||
mod distributed_storage;
|
||||
mod entity_cloning;
|
||||
mod runner;
|
||||
mod system_param;
|
||||
mod trigger_targets;
|
||||
|
||||
pub use entity_observer::ObservedBy;
|
||||
pub use centralized_storage::*;
|
||||
pub use distributed_storage::*;
|
||||
pub use runner::*;
|
||||
use variadics_please::all_tuples;
|
||||
pub use system_param::*;
|
||||
pub use trigger_targets::*;
|
||||
|
||||
use crate::{
|
||||
archetype::ArchetypeFlags,
|
||||
change_detection::MaybeLocation,
|
||||
component::ComponentId,
|
||||
entity::EntityHashMap,
|
||||
prelude::*,
|
||||
system::IntoObserverSystem,
|
||||
world::{DeferredWorld, *},
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_ptr::Ptr;
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
|
||||
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well. It also
|
||||
/// contains event propagation information. See [`On::propagate`] for more information.
|
||||
///
|
||||
/// The generic `B: Bundle` is used to modify the further specialize the events that this observer is interested in.
|
||||
/// The entity involved *does not* have to have these components, but the observer will only be
|
||||
/// triggered if the event matches the components in `B`.
|
||||
///
|
||||
/// This is used to to avoid providing a generic argument in your event, as is done for [`Add`]
|
||||
/// and the other lifecycle events.
|
||||
///
|
||||
/// Providing multiple components in this bundle will cause this event to be triggered by any
|
||||
/// matching component in the bundle,
|
||||
/// [rather than requiring all of them to be present](https://github.com/bevyengine/bevy/issues/15325).
|
||||
pub struct On<'w, E, B: Bundle = ()> {
|
||||
event: &'w mut E,
|
||||
propagate: &'w mut bool,
|
||||
trigger: ObserverTrigger,
|
||||
_marker: PhantomData<B>,
|
||||
}
|
||||
|
||||
/// Deprecated in favor of [`On`].
|
||||
#[deprecated(since = "0.17.0", note = "Renamed to `On`.")]
|
||||
pub type Trigger<'w, E, B = ()> = On<'w, E, B>;
|
||||
|
||||
impl<'w, E, B: Bundle> On<'w, E, B> {
|
||||
/// Creates a new instance of [`On`] for the given event and observer information.
|
||||
pub fn new(event: &'w mut E, propagate: &'w mut bool, trigger: ObserverTrigger) -> Self {
|
||||
Self {
|
||||
event,
|
||||
propagate,
|
||||
trigger,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the event type of this [`On`] instance.
|
||||
pub fn event_type(&self) -> ComponentId {
|
||||
self.trigger.event_type
|
||||
}
|
||||
|
||||
/// Returns a reference to the triggered event.
|
||||
pub fn event(&self) -> &E {
|
||||
self.event
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the triggered event.
|
||||
pub fn event_mut(&mut self) -> &mut E {
|
||||
self.event
|
||||
}
|
||||
|
||||
/// Returns a pointer to the triggered event.
|
||||
pub fn event_ptr(&self) -> Ptr {
|
||||
Ptr::from(&self.event)
|
||||
}
|
||||
|
||||
/// Returns the components that triggered the observer, out of the
|
||||
/// components defined in `B`. Does not necessarily include all of them as
|
||||
/// `B` acts like an `OR` filter rather than an `AND` filter.
|
||||
pub fn components(&self) -> &[ComponentId] {
|
||||
&self.trigger.components
|
||||
}
|
||||
|
||||
/// Returns the [`Entity`] that observed the triggered event.
|
||||
/// This allows you to despawn the observer, ceasing observation.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::prelude::{Commands, On};
|
||||
/// #
|
||||
/// # struct MyEvent {
|
||||
/// # done: bool,
|
||||
/// # }
|
||||
/// #
|
||||
/// /// Handle `MyEvent` and if it is done, stop observation.
|
||||
/// fn my_observer(trigger: On<MyEvent>, mut commands: Commands) {
|
||||
/// if trigger.event().done {
|
||||
/// commands.entity(trigger.observer()).despawn();
|
||||
/// return;
|
||||
/// }
|
||||
///
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
pub fn observer(&self) -> Entity {
|
||||
self.trigger.observer
|
||||
}
|
||||
|
||||
/// Returns the source code location that triggered this observer.
|
||||
pub fn caller(&self) -> MaybeLocation {
|
||||
self.trigger.caller
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E: EntityEvent, B: Bundle> On<'w, E, B> {
|
||||
/// Returns the [`Entity`] that was targeted by the `event` that triggered this observer.
|
||||
///
|
||||
/// Note that if event propagation is enabled, this may not be the same as the original target of the event,
|
||||
/// which can be accessed via [`On::original_target`].
|
||||
///
|
||||
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
|
||||
pub fn target(&self) -> Entity {
|
||||
self.trigger.current_target.unwrap_or(Entity::PLACEHOLDER)
|
||||
}
|
||||
|
||||
/// Returns the original [`Entity`] that the `event` was targeted at when it was first triggered.
|
||||
///
|
||||
/// If event propagation is not enabled, this will always return the same value as [`On::target`].
|
||||
///
|
||||
/// If the event was not targeted at a specific entity, this will return [`Entity::PLACEHOLDER`].
|
||||
pub fn original_target(&self) -> Entity {
|
||||
self.trigger.original_target.unwrap_or(Entity::PLACEHOLDER)
|
||||
}
|
||||
|
||||
/// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities.
|
||||
///
|
||||
/// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events
|
||||
/// use `()` which ends the path immediately and prevents propagation.
|
||||
///
|
||||
/// To enable propagation, you must:
|
||||
/// + Set [`EntityEvent::Traversal`] to the component you want to propagate along.
|
||||
/// + Either call `propagate(true)` in the first observer or set [`EntityEvent::AUTO_PROPAGATE`] to `true`.
|
||||
///
|
||||
/// You can prevent an event from propagating further using `propagate(false)`.
|
||||
///
|
||||
/// [`Traversal`]: crate::traversal::Traversal
|
||||
pub fn propagate(&mut self, should_propagate: bool) {
|
||||
*self.propagate = should_propagate;
|
||||
}
|
||||
|
||||
/// Returns the value of the flag that controls event propagation. See [`propagate`] for more information.
|
||||
///
|
||||
/// [`propagate`]: On::propagate
|
||||
pub fn get_propagate(&self) -> bool {
|
||||
*self.propagate
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E: Debug, B: Bundle> Debug for On<'w, E, B> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("On")
|
||||
.field("event", &self.event)
|
||||
.field("propagate", &self.propagate)
|
||||
.field("trigger", &self.trigger)
|
||||
.field("_marker", &self._marker)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E, B: Bundle> Deref for On<'w, E, B> {
|
||||
type Target = E;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.event
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, E, B: Bundle> DerefMut for On<'w, E, B> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.event
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a collection of targets for a specific [`On`] instance of an [`Event`].
|
||||
///
|
||||
/// When an event is triggered with [`TriggerTargets`], any [`Observer`] that watches for that specific
|
||||
/// event-target combination will run.
|
||||
///
|
||||
/// This trait is implemented for both [`Entity`] and [`ComponentId`], allowing you to target specific entities or components.
|
||||
/// It is also implemented for various collections of these types, such as [`Vec`], arrays, and tuples,
|
||||
/// allowing you to trigger events for multiple targets at once.
|
||||
pub trait TriggerTargets {
|
||||
/// The components the trigger should target.
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_;
|
||||
|
||||
/// The entities the trigger should target.
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_;
|
||||
}
|
||||
|
||||
impl<T: TriggerTargets + ?Sized> TriggerTargets for &T {
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
(**self).components()
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
(**self).entities()
|
||||
}
|
||||
}
|
||||
|
||||
impl TriggerTargets for Entity {
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
[].into_iter()
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
core::iter::once(*self)
|
||||
}
|
||||
}
|
||||
|
||||
impl TriggerTargets for ComponentId {
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
core::iter::once(*self)
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
[].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TriggerTargets> TriggerTargets for Vec<T> {
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
self.iter().flat_map(T::components)
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
self.iter().flat_map(T::entities)
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, T: TriggerTargets> TriggerTargets for [T; N] {
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
self.iter().flat_map(T::components)
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
self.iter().flat_map(T::entities)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TriggerTargets> TriggerTargets for [T] {
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
self.iter().flat_map(T::components)
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
self.iter().flat_map(T::entities)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_trigger_targets_tuples {
|
||||
($(#[$meta:meta])* $($trigger_targets: ident),*) => {
|
||||
#[expect(clippy::allow_attributes, reason = "can't guarantee violation of non_snake_case")]
|
||||
#[allow(non_snake_case, reason = "`all_tuples!()` generates non-snake-case variable names.")]
|
||||
$(#[$meta])*
|
||||
impl<$($trigger_targets: TriggerTargets),*> TriggerTargets for ($($trigger_targets,)*)
|
||||
{
|
||||
fn components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
let iter = [].into_iter();
|
||||
let ($($trigger_targets,)*) = self;
|
||||
$(
|
||||
let iter = iter.chain($trigger_targets.components());
|
||||
)*
|
||||
iter
|
||||
}
|
||||
|
||||
fn entities(&self) -> impl Iterator<Item = Entity> + Clone + '_ {
|
||||
let iter = [].into_iter();
|
||||
let ($($trigger_targets,)*) = self;
|
||||
$(
|
||||
let iter = iter.chain($trigger_targets.entities());
|
||||
)*
|
||||
iter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_tuples!(
|
||||
#[doc(fake_variadic)]
|
||||
impl_trigger_targets_tuples,
|
||||
0,
|
||||
15,
|
||||
T
|
||||
);
|
||||
|
||||
/// Store information about what an [`Observer`] observes.
|
||||
///
|
||||
/// This information is stored inside of the [`Observer`] component,
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ObserverDescriptor {
|
||||
/// The events the observer is watching.
|
||||
events: Vec<ComponentId>,
|
||||
|
||||
/// The components the observer is watching.
|
||||
components: Vec<ComponentId>,
|
||||
|
||||
/// The entities the observer is watching.
|
||||
entities: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl ObserverDescriptor {
|
||||
/// Add the given `events` to the descriptor.
|
||||
/// # Safety
|
||||
/// The type of each [`ComponentId`] in `events` _must_ match the actual value
|
||||
/// of the event passed into the observer.
|
||||
pub unsafe fn with_events(mut self, events: Vec<ComponentId>) -> Self {
|
||||
self.events = events;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add the given `components` to the descriptor.
|
||||
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
|
||||
self.components = components;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add the given `entities` to the descriptor.
|
||||
pub fn with_entities(mut self, entities: Vec<Entity>) -> Self {
|
||||
self.entities = entities;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the `events` that the observer is watching.
|
||||
pub fn events(&self) -> &[ComponentId] {
|
||||
&self.events
|
||||
}
|
||||
|
||||
/// Returns the `components` that the observer is watching.
|
||||
pub fn components(&self) -> &[ComponentId] {
|
||||
&self.components
|
||||
}
|
||||
|
||||
/// Returns the `entities` that the observer is watching.
|
||||
pub fn entities(&self) -> &[Entity] {
|
||||
&self.entities
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about a specific [`Event`] that triggered an observer.
|
||||
///
|
||||
/// This information is exposed via methods on [`On`].
|
||||
#[derive(Debug)]
|
||||
pub struct ObserverTrigger {
|
||||
/// The [`Entity`] of the observer handling the trigger.
|
||||
pub observer: Entity,
|
||||
/// The [`Event`] the trigger targeted.
|
||||
pub event_type: ComponentId,
|
||||
/// The [`ComponentId`]s the trigger targeted.
|
||||
components: SmallVec<[ComponentId; 2]>,
|
||||
/// The entity that the entity-event targeted, if any.
|
||||
///
|
||||
/// Note that if event propagation is enabled, this may not be the same as [`ObserverTrigger::original_target`].
|
||||
pub current_target: Option<Entity>,
|
||||
/// The entity that the entity-event was originally targeted at, if any.
|
||||
///
|
||||
/// If event propagation is enabled, this will be the first entity that the event was targeted at,
|
||||
/// even if the event was propagated to other entities.
|
||||
pub original_target: Option<Entity>,
|
||||
/// The location of the source code that triggered the observer.
|
||||
pub caller: MaybeLocation,
|
||||
}
|
||||
|
||||
impl ObserverTrigger {
|
||||
/// Returns the components that the trigger targeted.
|
||||
pub fn components(&self) -> &[ComponentId] {
|
||||
&self.components
|
||||
}
|
||||
}
|
||||
|
||||
/// Map between an observer entity and its [`ObserverRunner`]
|
||||
pub type ObserverMap = EntityHashMap<ObserverRunner>;
|
||||
|
||||
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event targeted at a specific component.
|
||||
///
|
||||
/// This is stored inside of [`CachedObservers`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct CachedComponentObservers {
|
||||
// Observers listening to events targeting this component, but not a specific entity
|
||||
global_observers: ObserverMap,
|
||||
// Observers listening to events targeting this component on a specific entity
|
||||
entity_component_observers: EntityHashMap<ObserverMap>,
|
||||
}
|
||||
|
||||
impl CachedComponentObservers {
|
||||
/// Returns the observers listening for this trigger, regardless of target.
|
||||
/// These observers will also respond to events targeting specific entities.
|
||||
pub fn global_observers(&self) -> &ObserverMap {
|
||||
&self.global_observers
|
||||
}
|
||||
|
||||
/// Returns the observers listening for this trigger targeting this component on a specific entity.
|
||||
pub fn entity_component_observers(&self) -> &EntityHashMap<ObserverMap> {
|
||||
&self.entity_component_observers
|
||||
}
|
||||
}
|
||||
|
||||
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular event.
|
||||
///
|
||||
/// This is stored inside of [`Observers`], specialized for each kind of observer.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct CachedObservers {
|
||||
// Observers listening for any time this event is fired, regardless of target
|
||||
// This will also respond to events targeting specific components or entities
|
||||
global_observers: ObserverMap,
|
||||
// Observers listening for this trigger fired at a specific component
|
||||
component_observers: HashMap<ComponentId, CachedComponentObservers>,
|
||||
// Observers listening for this trigger fired at a specific entity
|
||||
entity_observers: EntityHashMap<ObserverMap>,
|
||||
}
|
||||
|
||||
impl CachedObservers {
|
||||
/// Returns the observers listening for this trigger, regardless of target.
|
||||
/// These observers will also respond to events targeting specific components or entities.
|
||||
pub fn global_observers(&self) -> &ObserverMap {
|
||||
&self.global_observers
|
||||
}
|
||||
|
||||
/// Returns the observers listening for this trigger targeting components.
|
||||
pub fn get_component_observers(&self) -> &HashMap<ComponentId, CachedComponentObservers> {
|
||||
&self.component_observers
|
||||
}
|
||||
|
||||
/// Returns the observers listening for this trigger targeting entities.
|
||||
pub fn entity_observers(&self) -> &HashMap<ComponentId, CachedComponentObservers> {
|
||||
&self.component_observers
|
||||
}
|
||||
}
|
||||
|
||||
/// An internal lookup table tracking all of the observers in the world.
|
||||
///
|
||||
/// Stores a cache mapping trigger ids to the registered observers.
|
||||
/// Some observer kinds (like [lifecycle](crate::lifecycle) observers) have a dedicated field,
|
||||
/// saving lookups for the most common triggers.
|
||||
///
|
||||
/// This can be accessed via [`World::observers`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Observers {
|
||||
// Cached ECS observers to save a lookup most common triggers.
|
||||
add: CachedObservers,
|
||||
insert: CachedObservers,
|
||||
replace: CachedObservers,
|
||||
remove: CachedObservers,
|
||||
despawn: CachedObservers,
|
||||
// Map from trigger type to set of observers listening to that trigger
|
||||
cache: HashMap<ComponentId, CachedObservers>,
|
||||
}
|
||||
|
||||
impl Observers {
|
||||
pub(crate) fn get_observers_mut(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ADD => &mut self.add,
|
||||
INSERT => &mut self.insert,
|
||||
REPLACE => &mut self.replace,
|
||||
REMOVE => &mut self.remove,
|
||||
DESPAWN => &mut self.despawn,
|
||||
_ => self.cache.entry(event_type).or_default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to get the observers for the given `event_type`.
|
||||
///
|
||||
/// When accessing the observers for lifecycle events, such as [`Add`], [`Insert`], [`Replace`], [`Remove`], and [`Despawn`],
|
||||
/// use the [`ComponentId`] constants from the [`lifecycle`](crate::lifecycle) module.
|
||||
pub fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ADD => Some(&self.add),
|
||||
INSERT => Some(&self.insert),
|
||||
REPLACE => Some(&self.replace),
|
||||
REMOVE => Some(&self.remove),
|
||||
DESPAWN => Some(&self.despawn),
|
||||
_ => self.cache.get(&event_type),
|
||||
}
|
||||
}
|
||||
|
||||
/// This will run the observers of the given `event_type`, targeting the given `entity` and `components`.
|
||||
pub(crate) fn invoke<T>(
|
||||
mut world: DeferredWorld,
|
||||
event_type: ComponentId,
|
||||
current_target: Option<Entity>,
|
||||
original_target: Option<Entity>,
|
||||
components: impl Iterator<Item = ComponentId> + Clone,
|
||||
data: &mut T,
|
||||
propagate: &mut bool,
|
||||
caller: MaybeLocation,
|
||||
) {
|
||||
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
|
||||
let (mut world, observers) = unsafe {
|
||||
let world = world.as_unsafe_world_cell();
|
||||
// SAFETY: There are no outstanding world references
|
||||
world.increment_trigger_id();
|
||||
let observers = world.observers();
|
||||
let Some(observers) = observers.try_get_observers(event_type) else {
|
||||
return;
|
||||
};
|
||||
// SAFETY: The only outstanding reference to world is `observers`
|
||||
(world.into_deferred(), observers)
|
||||
};
|
||||
|
||||
let trigger_for_components = components.clone();
|
||||
|
||||
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
|
||||
(runner)(
|
||||
world.reborrow(),
|
||||
ObserverTrigger {
|
||||
observer,
|
||||
event_type,
|
||||
components: components.clone().collect(),
|
||||
current_target,
|
||||
original_target,
|
||||
caller,
|
||||
},
|
||||
data.into(),
|
||||
propagate,
|
||||
);
|
||||
};
|
||||
// Trigger observers listening for any kind of this trigger
|
||||
observers
|
||||
.global_observers
|
||||
.iter()
|
||||
.for_each(&mut trigger_observer);
|
||||
|
||||
// Trigger entity observers listening for this kind of trigger
|
||||
if let Some(target_entity) = current_target {
|
||||
if let Some(map) = observers.entity_observers.get(&target_entity) {
|
||||
map.iter().for_each(&mut trigger_observer);
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger observers listening to this trigger targeting a specific component
|
||||
trigger_for_components.for_each(|id| {
|
||||
if let Some(component_observers) = observers.component_observers.get(&id) {
|
||||
component_observers
|
||||
.global_observers
|
||||
.iter()
|
||||
.for_each(&mut trigger_observer);
|
||||
|
||||
if let Some(target_entity) = current_target {
|
||||
if let Some(map) = component_observers
|
||||
.entity_component_observers
|
||||
.get(&target_entity)
|
||||
{
|
||||
map.iter().for_each(&mut trigger_observer);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
||||
INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
||||
REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
|
||||
REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
|
||||
DESPAWN => Some(ArchetypeFlags::ON_DESPAWN_OBSERVER),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_archetype_flags(
|
||||
&self,
|
||||
component_id: ComponentId,
|
||||
flags: &mut ArchetypeFlags,
|
||||
) {
|
||||
if self.add.component_observers.contains_key(&component_id) {
|
||||
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
|
||||
}
|
||||
|
||||
if self.insert.component_observers.contains_key(&component_id) {
|
||||
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
|
||||
}
|
||||
|
||||
if self.replace.component_observers.contains_key(&component_id) {
|
||||
flags.insert(ArchetypeFlags::ON_REPLACE_OBSERVER);
|
||||
}
|
||||
|
||||
if self.remove.component_observers.contains_key(&component_id) {
|
||||
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
|
||||
}
|
||||
|
||||
if self.despawn.component_observers.contains_key(&component_id) {
|
||||
flags.insert(ArchetypeFlags::ON_DESPAWN_OBSERVER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl World {
|
||||
/// Spawns a "global" [`Observer`] which will watch for the given event.
|
||||
@ -1315,7 +727,7 @@ mod tests {
|
||||
world.spawn(A).flush();
|
||||
assert_eq!(vec!["add_2", "add_1"], world.resource::<Order>().0);
|
||||
// Our A entity plus our two observers
|
||||
assert_eq!(world.entities().count_constructed(), 3);
|
||||
assert_eq!(world.entity_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
use alloc::{boxed::Box, vec};
|
||||
use bevy_utils::prelude::DebugName;
|
||||
//! Logic for evaluating observers, and storing functions inside of observers.
|
||||
|
||||
use core::any::Any;
|
||||
|
||||
use crate::{
|
||||
component::{ComponentId, Mutable, StorageType},
|
||||
error::{ErrorContext, ErrorHandler},
|
||||
lifecycle::{ComponentHook, HookContext},
|
||||
observer::{ObserverDescriptor, ObserverTrigger},
|
||||
prelude::*,
|
||||
query::DebugCheckedUnwrap,
|
||||
system::{IntoObserverSystem, ObserverSystem},
|
||||
world::DeferredWorld,
|
||||
error::ErrorContext, observer::ObserverTrigger, prelude::*, query::DebugCheckedUnwrap,
|
||||
system::ObserverSystem, world::DeferredWorld,
|
||||
};
|
||||
use bevy_ptr::PtrMut;
|
||||
|
||||
@ -20,323 +14,7 @@ use bevy_ptr::PtrMut;
|
||||
/// but can be overridden for custom behavior.
|
||||
pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut, propagate: &mut bool);
|
||||
|
||||
/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer".
|
||||
///
|
||||
/// Observers listen for a "trigger" of a specific [`Event`]. An event can be triggered on the [`World`]
|
||||
/// by calling [`World::trigger`], or if the event is an [`EntityEvent`], it can also be triggered for specific
|
||||
/// entity targets using [`World::trigger_targets`].
|
||||
///
|
||||
/// Note that [`BufferedEvent`]s sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered.
|
||||
/// They must be triggered at a specific point in the schedule.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// The simplest usage of the observer pattern looks like this:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// #[derive(Event)]
|
||||
/// struct Speak {
|
||||
/// message: String,
|
||||
/// }
|
||||
///
|
||||
/// world.add_observer(|trigger: On<Speak>| {
|
||||
/// println!("{}", trigger.event().message);
|
||||
/// });
|
||||
///
|
||||
/// // Observers currently require a flush() to be registered. In the context of schedules,
|
||||
/// // this will generally be done for you.
|
||||
/// world.flush();
|
||||
///
|
||||
/// world.trigger(Speak {
|
||||
/// message: "Hello!".into(),
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Notice that we used [`World::add_observer`]. This is just a shorthand for spawning an [`Observer`] manually:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # #[derive(Event)]
|
||||
/// # struct Speak;
|
||||
/// // These are functionally the same:
|
||||
/// world.add_observer(|trigger: On<Speak>| {});
|
||||
/// world.spawn(Observer::new(|trigger: On<Speak>| {}));
|
||||
/// ```
|
||||
///
|
||||
/// Observers are systems. They can access arbitrary [`World`] data by adding [`SystemParam`]s:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # #[derive(Event)]
|
||||
/// # struct PrintNames;
|
||||
/// # #[derive(Component, Debug)]
|
||||
/// # struct Name;
|
||||
/// world.add_observer(|trigger: On<PrintNames>, names: Query<&Name>| {
|
||||
/// for name in &names {
|
||||
/// println!("{name:?}");
|
||||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Note that [`On`] must always be the first parameter.
|
||||
///
|
||||
/// You can also add [`Commands`], which means you can spawn new entities, insert new components, etc:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # #[derive(Event)]
|
||||
/// # struct SpawnThing;
|
||||
/// # #[derive(Component, Debug)]
|
||||
/// # struct Thing;
|
||||
/// world.add_observer(|trigger: On<SpawnThing>, mut commands: Commands| {
|
||||
/// commands.spawn(Thing);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Observers can also trigger new events:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # #[derive(Event)]
|
||||
/// # struct A;
|
||||
/// # #[derive(Event)]
|
||||
/// # struct B;
|
||||
/// world.add_observer(|trigger: On<A>, mut commands: Commands| {
|
||||
/// commands.trigger(B);
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// When the commands are flushed (including these "nested triggers") they will be
|
||||
/// recursively evaluated until there are no commands left, meaning nested triggers all
|
||||
/// evaluate at the same time!
|
||||
///
|
||||
/// If the event is an [`EntityEvent`], it can be triggered for specific entities,
|
||||
/// which will be passed to the [`Observer`]:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # let entity = world.spawn_empty().id();
|
||||
/// #[derive(Event, EntityEvent)]
|
||||
/// struct Explode;
|
||||
///
|
||||
/// world.add_observer(|trigger: On<Explode>, mut commands: Commands| {
|
||||
/// println!("Entity {} goes BOOM!", trigger.target());
|
||||
/// commands.entity(trigger.target()).despawn();
|
||||
/// });
|
||||
///
|
||||
/// world.flush();
|
||||
///
|
||||
/// world.trigger_targets(Explode, entity);
|
||||
/// ```
|
||||
///
|
||||
/// You can trigger multiple entities at once:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # let e1 = world.spawn_empty().id();
|
||||
/// # let e2 = world.spawn_empty().id();
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Explode;
|
||||
/// world.trigger_targets(Explode, [e1, e2]);
|
||||
/// ```
|
||||
///
|
||||
/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Component, Debug)]
|
||||
/// # struct Name(String);
|
||||
/// # let mut world = World::default();
|
||||
/// # let e1 = world.spawn_empty().id();
|
||||
/// # let e2 = world.spawn_empty().id();
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Explode;
|
||||
/// world.entity_mut(e1).observe(|trigger: On<Explode>, mut commands: Commands| {
|
||||
/// println!("Boom!");
|
||||
/// commands.entity(trigger.target()).despawn();
|
||||
/// });
|
||||
///
|
||||
/// world.entity_mut(e2).observe(|trigger: On<Explode>, mut commands: Commands| {
|
||||
/// println!("The explosion fizzles! This entity is immune!");
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned.
|
||||
/// This protects against observer "garbage" building up over time.
|
||||
///
|
||||
/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again)
|
||||
/// just shorthand for spawning an [`Observer`] directly:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # let mut world = World::default();
|
||||
/// # let entity = world.spawn_empty().id();
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Explode;
|
||||
/// let mut observer = Observer::new(|trigger: On<Explode>| {});
|
||||
/// observer.watch_entity(entity);
|
||||
/// world.spawn(observer);
|
||||
/// ```
|
||||
///
|
||||
/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities!
|
||||
///
|
||||
/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`].
|
||||
/// serves as the "source of truth" of the observer.
|
||||
///
|
||||
/// [`SystemParam`]: crate::system::SystemParam
|
||||
pub struct Observer {
|
||||
hook_on_add: ComponentHook,
|
||||
error_handler: Option<ErrorHandler>,
|
||||
system: Box<dyn AnyNamedSystem>,
|
||||
pub(crate) descriptor: ObserverDescriptor,
|
||||
pub(crate) last_trigger_id: u32,
|
||||
pub(crate) despawned_watched_entities: u32,
|
||||
pub(crate) runner: ObserverRunner,
|
||||
}
|
||||
|
||||
impl Observer {
|
||||
/// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered
|
||||
/// for _any_ entity (or no entity).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the given system is an exclusive system.
|
||||
pub fn new<E: Event, B: Bundle, M, I: IntoObserverSystem<E, B, M>>(system: I) -> Self {
|
||||
let system = Box::new(IntoObserverSystem::into_system(system));
|
||||
assert!(
|
||||
!system.is_exclusive(),
|
||||
concat!(
|
||||
"Exclusive system `{}` may not be used as observer.\n",
|
||||
"Instead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
|
||||
),
|
||||
system.name()
|
||||
);
|
||||
Self {
|
||||
system,
|
||||
descriptor: Default::default(),
|
||||
hook_on_add: hook_on_add::<E, B, I::System>,
|
||||
error_handler: None,
|
||||
runner: observer_system_runner::<E, B, I::System>,
|
||||
despawned_watched_entities: 0,
|
||||
last_trigger_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`Observer`] with custom runner, this is mostly used for dynamic event observer
|
||||
pub fn with_dynamic_runner(runner: ObserverRunner) -> Self {
|
||||
Self {
|
||||
system: Box::new(IntoSystem::into_system(|| {})),
|
||||
descriptor: Default::default(),
|
||||
hook_on_add: |mut world, hook_context| {
|
||||
let default_error_handler = world.default_error_handler();
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
let entity = hook_context.entity;
|
||||
if let Some(mut observe) = world.get_mut::<Observer>(entity) {
|
||||
if observe.descriptor.events.is_empty() {
|
||||
return;
|
||||
}
|
||||
if observe.error_handler.is_none() {
|
||||
observe.error_handler = Some(default_error_handler);
|
||||
}
|
||||
world.register_observer(entity);
|
||||
}
|
||||
});
|
||||
},
|
||||
error_handler: None,
|
||||
runner,
|
||||
despawned_watched_entities: 0,
|
||||
last_trigger_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
|
||||
/// for the `entity`.
|
||||
pub fn with_entity(mut self, entity: Entity) -> Self {
|
||||
self.descriptor.entities.push(entity);
|
||||
self
|
||||
}
|
||||
|
||||
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
|
||||
/// for the `entity`.
|
||||
/// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects.
|
||||
pub fn watch_entity(&mut self, entity: Entity) {
|
||||
self.descriptor.entities.push(entity);
|
||||
}
|
||||
|
||||
/// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
|
||||
/// with the given component target.
|
||||
pub fn with_component(mut self, component: ComponentId) -> Self {
|
||||
self.descriptor.components.push(component);
|
||||
self
|
||||
}
|
||||
|
||||
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
|
||||
/// is triggered.
|
||||
/// # Safety
|
||||
/// The type of the `event` [`ComponentId`] _must_ match the actual value
|
||||
/// of the event passed into the observer system.
|
||||
pub unsafe fn with_event(mut self, event: ComponentId) -> Self {
|
||||
self.descriptor.events.push(event);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the error handler to use for this observer.
|
||||
///
|
||||
/// See the [`error` module-level documentation](crate::error) for more information.
|
||||
pub fn with_error_handler(mut self, error_handler: fn(BevyError, ErrorContext)) -> Self {
|
||||
self.error_handler = Some(error_handler);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the [`ObserverDescriptor`] for this [`Observer`].
|
||||
pub fn descriptor(&self) -> &ObserverDescriptor {
|
||||
&self.descriptor
|
||||
}
|
||||
|
||||
/// Returns the name of the [`Observer`]'s system .
|
||||
pub fn system_name(&self) -> DebugName {
|
||||
self.system.system_name()
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for Observer {
|
||||
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
|
||||
type Mutability = Mutable;
|
||||
fn on_add() -> Option<ComponentHook> {
|
||||
Some(|world, context| {
|
||||
let Some(observe) = world.get::<Self>(context.entity) else {
|
||||
return;
|
||||
};
|
||||
let hook = observe.hook_on_add;
|
||||
hook(world, context);
|
||||
})
|
||||
}
|
||||
fn on_remove() -> Option<ComponentHook> {
|
||||
Some(|mut world, HookContext { entity, .. }| {
|
||||
let descriptor = core::mem::take(
|
||||
&mut world
|
||||
.entity_mut(entity)
|
||||
.get_mut::<Self>()
|
||||
.unwrap()
|
||||
.as_mut()
|
||||
.descriptor,
|
||||
);
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
world.unregister_observer(entity, descriptor);
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
pub(super) fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
mut world: DeferredWorld,
|
||||
observer_trigger: ObserverTrigger,
|
||||
ptr: PtrMut,
|
||||
@ -420,48 +98,6 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
}
|
||||
}
|
||||
|
||||
trait AnyNamedSystem: Any + Send + Sync + 'static {
|
||||
fn system_name(&self) -> DebugName;
|
||||
}
|
||||
|
||||
impl<T: Any + System> AnyNamedSystem for T {
|
||||
fn system_name(&self) -> DebugName {
|
||||
self.name()
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::lifecycle::ComponentHooks::on_add`).
|
||||
///
|
||||
/// This function exists separate from [`Observer`] to allow [`Observer`] to have its type parameters
|
||||
/// erased.
|
||||
///
|
||||
/// The type parameters of this function _must_ match those used to create the [`Observer`].
|
||||
/// As such, it is recommended to only use this function within the [`Observer::new`] method to
|
||||
/// ensure type parameters match.
|
||||
fn hook_on_add<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
mut world: DeferredWorld<'_>,
|
||||
HookContext { entity, .. }: HookContext,
|
||||
) {
|
||||
world.commands().queue(move |world: &mut World| {
|
||||
let event_id = E::register_component_id(world);
|
||||
let mut components = vec![];
|
||||
B::component_ids(&mut world.components_registrator(), &mut |id| {
|
||||
components.push(id);
|
||||
});
|
||||
if let Some(mut observer) = world.get_mut::<Observer>(entity) {
|
||||
observer.descriptor.events.push(event_id);
|
||||
observer.descriptor.components.extend(components);
|
||||
|
||||
let system: &mut dyn Any = observer.system.as_mut();
|
||||
let system: *mut dyn ObserverSystem<E, B> = system.downcast_mut::<S>().unwrap();
|
||||
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
|
||||
unsafe {
|
||||
(*system).initialize(world);
|
||||
}
|
||||
world.register_observer(entity);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -516,9 +152,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Exclusive system `bevy_ecs::observer::runner::tests::exclusive_system_cannot_be_observer::system` may not be used as observer.\nInstead of `&mut World`, use either `DeferredWorld` if you do not need structural changes, or `Commands` if you do."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn exclusive_system_cannot_be_observer() {
|
||||
fn system(_: On<TriggerEvent>, _world: &mut World) {}
|
||||
let mut world = World::default();
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Copy for ReadFetch<'_, T> {}
|
||||
|
||||
/// SAFETY:
|
||||
@ -1665,6 +1666,7 @@ impl<T: Component> Clone for RefFetch<'_, T> {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Copy for RefFetch<'_, T> {}
|
||||
|
||||
/// SAFETY:
|
||||
@ -1873,6 +1875,7 @@ impl<T: Component> Clone for WriteFetch<'_, T> {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Copy for WriteFetch<'_, T> {}
|
||||
|
||||
/// SAFETY:
|
||||
|
||||
@ -1240,6 +1240,7 @@ unsafe impl QueryFilter for Spawned {
|
||||
pub trait ArchetypeFilter: QueryFilter {}
|
||||
|
||||
impl<T: Component> ArchetypeFilter for With<T> {}
|
||||
|
||||
impl<T: Component> ArchetypeFilter for Without<T> {}
|
||||
|
||||
macro_rules! impl_archetype_filter_tuple {
|
||||
|
||||
@ -507,7 +507,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."]
|
||||
#[should_panic]
|
||||
fn self_conflicting_worldquery() {
|
||||
#[derive(QueryData)]
|
||||
#[query_data(mutable)]
|
||||
|
||||
@ -1901,9 +1901,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Transmuted state for ((&bevy_ecs::query::state::tests::A, &bevy_ecs::query::state::tests::B), ()) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_transmute_to_include_data_not_in_original_query() {
|
||||
let mut world = World::new();
|
||||
world.register_component::<A>();
|
||||
@ -1915,9 +1913,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Transmuted state for (&mut bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_transmute_immut_to_mut() {
|
||||
let mut world = World::new();
|
||||
world.spawn(A(0));
|
||||
@ -1927,9 +1923,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Transmuted state for (&bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (core::option::Option<&bevy_ecs::query::state::tests::A>, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_transmute_option_to_immut() {
|
||||
let mut world = World::new();
|
||||
world.spawn(C(0));
|
||||
@ -1941,9 +1935,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Transmuted state for (&bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (bevy_ecs::world::entity_ref::EntityRef, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_transmute_entity_ref() {
|
||||
let mut world = World::new();
|
||||
world.register_component::<A>();
|
||||
@ -2009,9 +2001,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Transmuted state for (bevy_ecs::entity::Entity, bevy_ecs::query::filter::Changed<bevy_ecs::query::state::tests::B>) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_transmute_changed_without_access() {
|
||||
let mut world = World::new();
|
||||
world.register_component::<A>();
|
||||
@ -2021,9 +2011,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Transmuted state for (&mut bevy_ecs::query::state::tests::A, ()) attempts to access terms that are not allowed by original state (&bevy_ecs::query::state::tests::A, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_transmute_mutable_after_readonly() {
|
||||
let mut world = World::new();
|
||||
// Calling this method would mean we had aliasing queries.
|
||||
@ -2130,9 +2118,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Joined state for (&bevy_ecs::query::state::tests::C, ()) \
|
||||
attempts to access terms that are not allowed by state \
|
||||
(&bevy_ecs::query::state::tests::A, ()) joined with (&bevy_ecs::query::state::tests::B, ()).")]
|
||||
#[should_panic]
|
||||
fn cannot_join_wrong_fetch() {
|
||||
let mut world = World::new();
|
||||
world.register_component::<C>();
|
||||
@ -2142,12 +2128,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Joined state for (bevy_ecs::entity::Entity, bevy_ecs::query::filter::Changed<bevy_ecs::query::state::tests::C>) \
|
||||
attempts to access terms that are not allowed by state \
|
||||
(&bevy_ecs::query::state::tests::A, bevy_ecs::query::filter::Without<bevy_ecs::query::state::tests::C>) \
|
||||
joined with (&bevy_ecs::query::state::tests::B, bevy_ecs::query::filter::Without<bevy_ecs::query::state::tests::C>)."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_join_wrong_filter() {
|
||||
let mut world = World::new();
|
||||
let query_1 = QueryState::<&A, Without<C>>::new(&mut world);
|
||||
@ -2156,9 +2137,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Joined state for ((&mut bevy_ecs::query::state::tests::A, &mut bevy_ecs::query::state::tests::B), ()) attempts to access terms that are not allowed by state (&bevy_ecs::query::state::tests::A, ()) joined with (&mut bevy_ecs::query::state::tests::B, ())."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn cannot_join_mutable_after_readonly() {
|
||||
let mut world = World::new();
|
||||
// Calling this method would mean we had aliasing queries.
|
||||
|
||||
@ -82,6 +82,20 @@ pub trait Relationship: Component + Sized {
|
||||
/// Creates this [`Relationship`] from the given `entity`.
|
||||
fn from(entity: Entity) -> Self;
|
||||
|
||||
/// Changes the current [`Entity`] ID of the entity containing the [`RelationshipTarget`] to another one.
|
||||
///
|
||||
/// This is useful for updating the relationship without overwriting other fields stored in `Self`.
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This should generally not be called by user code, as modifying the related entity could invalidate the
|
||||
/// relationship. If this method is used, then the hooks [`on_replace`](Relationship::on_replace) have to
|
||||
/// run before and [`on_insert`](Relationship::on_insert) after it.
|
||||
/// This happens automatically when this method is called with [`EntityWorldMut::modify_component`].
|
||||
///
|
||||
/// Prefer to use regular means of insertions when possible.
|
||||
fn set_risky(&mut self, entity: Entity);
|
||||
|
||||
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||
fn on_insert(
|
||||
mut world: DeferredWorld,
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::{
|
||||
Relationship, RelationshipHookMode, RelationshipSourceCollection, RelationshipTarget,
|
||||
},
|
||||
system::{Commands, EntityCommands},
|
||||
world::{EntityWorldMut, World},
|
||||
world::{DeferredWorld, EntityWorldMut, World},
|
||||
};
|
||||
use bevy_platform::prelude::{Box, Vec};
|
||||
use core::{marker::PhantomData, mem};
|
||||
@ -42,7 +42,12 @@ impl<'w> EntityWorldMut<'w> {
|
||||
let id = self.id();
|
||||
self.world_scope(|world| {
|
||||
for related in related {
|
||||
world.entity_mut(*related).insert(R::from(id));
|
||||
world
|
||||
.entity_mut(*related)
|
||||
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
|
||||
id,
|
||||
RelationshipHookMode::Run,
|
||||
);
|
||||
}
|
||||
});
|
||||
self
|
||||
@ -98,7 +103,12 @@ impl<'w> EntityWorldMut<'w> {
|
||||
.collection_mut_risky()
|
||||
.place(*related, index);
|
||||
} else {
|
||||
world.entity_mut(*related).insert(R::from(id));
|
||||
world
|
||||
.entity_mut(*related)
|
||||
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
|
||||
id,
|
||||
RelationshipHookMode::Run,
|
||||
);
|
||||
world
|
||||
.get_mut::<R::RelationshipTarget>(id)
|
||||
.expect("hooks should have added relationship target")
|
||||
@ -165,10 +175,13 @@ impl<'w> EntityWorldMut<'w> {
|
||||
}
|
||||
|
||||
for related in potential_relations {
|
||||
// SAFETY: We'll manually be adjusting the contents of the parent to fit the final state.
|
||||
// SAFETY: We'll manually be adjusting the contents of the `RelationshipTarget` to fit the final state.
|
||||
world
|
||||
.entity_mut(related)
|
||||
.insert_with_relationship_hook_mode(R::from(id), RelationshipHookMode::Skip);
|
||||
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
|
||||
id,
|
||||
RelationshipHookMode::Skip,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -266,7 +279,10 @@ impl<'w> EntityWorldMut<'w> {
|
||||
// We changed the target collection manually so don't run the insert hook
|
||||
world
|
||||
.entity_mut(*new_relation)
|
||||
.insert_with_relationship_hook_mode(R::from(this), RelationshipHookMode::Skip);
|
||||
.modify_or_insert_relation_with_relationship_hook_mode::<R>(
|
||||
this,
|
||||
RelationshipHookMode::Skip,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@ -352,6 +368,40 @@ impl<'w> EntityWorldMut<'w> {
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
fn modify_or_insert_relation_with_relationship_hook_mode<R: Relationship>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
relationship_hook_mode: RelationshipHookMode,
|
||||
) {
|
||||
// Check if the relation edge holds additional data
|
||||
if size_of::<R>() > size_of::<Entity>() {
|
||||
self.assert_not_despawned();
|
||||
|
||||
let this = self.id();
|
||||
|
||||
let modified = self.world_scope(|world| {
|
||||
let modified = DeferredWorld::from(&mut *world)
|
||||
.modify_component_with_relationship_hook_mode::<R, _>(
|
||||
this,
|
||||
relationship_hook_mode,
|
||||
|r| r.set_risky(entity),
|
||||
)
|
||||
.expect("entity access must be valid")
|
||||
.is_some();
|
||||
|
||||
world.flush();
|
||||
|
||||
modified
|
||||
});
|
||||
|
||||
if modified {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
self.insert_with_relationship_hook_mode(R::from(entity), relationship_hook_mode);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> EntityCommands<'a> {
|
||||
@ -689,17 +739,132 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_related_keeps_data() {
|
||||
#[derive(Component)]
|
||||
fn add_related_keeps_relationship_data() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
#[relationship(relationship_target = Parent)]
|
||||
pub struct Child(Entity);
|
||||
struct Child {
|
||||
#[relationship]
|
||||
parent: Entity,
|
||||
data: u8,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[relationship_target(relationship = Child)]
|
||||
pub struct Parent {
|
||||
struct Parent(Vec<Entity>);
|
||||
|
||||
let mut world = World::new();
|
||||
let parent1 = world.spawn_empty().id();
|
||||
let parent2 = world.spawn_empty().id();
|
||||
let child = world
|
||||
.spawn(Child {
|
||||
parent: parent1,
|
||||
data: 42,
|
||||
})
|
||||
.id();
|
||||
|
||||
world.entity_mut(parent2).add_related::<Child>(&[child]);
|
||||
assert_eq!(
|
||||
world.get::<Child>(child),
|
||||
Some(&Child {
|
||||
parent: parent2,
|
||||
data: 42
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_related_keeps_relationship_data() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
#[relationship(relationship_target = Parent)]
|
||||
struct Child {
|
||||
#[relationship]
|
||||
parent: Entity,
|
||||
data: u8,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[relationship_target(relationship = Child)]
|
||||
struct Parent(Vec<Entity>);
|
||||
|
||||
let mut world = World::new();
|
||||
let parent1 = world.spawn_empty().id();
|
||||
let parent2 = world.spawn_empty().id();
|
||||
let child = world
|
||||
.spawn(Child {
|
||||
parent: parent1,
|
||||
data: 42,
|
||||
})
|
||||
.id();
|
||||
|
||||
world
|
||||
.entity_mut(parent2)
|
||||
.insert_related::<Child>(0, &[child]);
|
||||
assert_eq!(
|
||||
world.get::<Child>(child),
|
||||
Some(&Child {
|
||||
parent: parent2,
|
||||
data: 42
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_related_keeps_relationship_data() {
|
||||
#[derive(Component, PartialEq, Debug)]
|
||||
#[relationship(relationship_target = Parent)]
|
||||
struct Child {
|
||||
#[relationship]
|
||||
parent: Entity,
|
||||
data: u8,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[relationship_target(relationship = Child)]
|
||||
struct Parent(Vec<Entity>);
|
||||
|
||||
let mut world = World::new();
|
||||
let parent1 = world.spawn_empty().id();
|
||||
let parent2 = world.spawn_empty().id();
|
||||
let child = world
|
||||
.spawn(Child {
|
||||
parent: parent1,
|
||||
data: 42,
|
||||
})
|
||||
.id();
|
||||
|
||||
world
|
||||
.entity_mut(parent2)
|
||||
.replace_related_with_difference::<Child>(&[], &[child], &[child]);
|
||||
assert_eq!(
|
||||
world.get::<Child>(child),
|
||||
Some(&Child {
|
||||
parent: parent2,
|
||||
data: 42
|
||||
})
|
||||
);
|
||||
|
||||
world.entity_mut(parent1).replace_related::<Child>(&[child]);
|
||||
assert_eq!(
|
||||
world.get::<Child>(child),
|
||||
Some(&Child {
|
||||
parent: parent1,
|
||||
data: 42
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn replace_related_keeps_relationship_target_data() {
|
||||
#[derive(Component)]
|
||||
#[relationship(relationship_target = Parent)]
|
||||
struct Child(Entity);
|
||||
|
||||
#[derive(Component)]
|
||||
#[relationship_target(relationship = Child)]
|
||||
struct Parent {
|
||||
#[relationship]
|
||||
children: Vec<Entity>,
|
||||
pub data: u8,
|
||||
data: u8,
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
|
||||
@ -26,7 +26,9 @@ pub mod passes {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use alloc::{string::ToString, vec, vec::Vec};
|
||||
#[cfg(feature = "trace")]
|
||||
use alloc::string::ToString;
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use crate::error::BevyError;
|
||||
@ -770,6 +772,7 @@ mod tests {
|
||||
}
|
||||
|
||||
mod system_ambiguity {
|
||||
#[cfg(feature = "trace")]
|
||||
use alloc::collections::BTreeSet;
|
||||
|
||||
use super::*;
|
||||
@ -1110,6 +1113,7 @@ mod tests {
|
||||
|
||||
// Tests that the correct ambiguities were reported in the correct order.
|
||||
#[test]
|
||||
#[cfg(feature = "trace")]
|
||||
fn correct_ambiguities() {
|
||||
fn system_a(_res: ResMut<R>) {}
|
||||
fn system_b(_res: ResMut<R>) {}
|
||||
@ -1183,6 +1187,7 @@ mod tests {
|
||||
// Test that anonymous set names work properly
|
||||
// Related issue https://github.com/bevyengine/bevy/issues/9641
|
||||
#[test]
|
||||
#[cfg(feature = "trace")]
|
||||
fn anonymous_set_name() {
|
||||
let mut schedule = Schedule::new(TestSchedule);
|
||||
schedule.add_systems((resmut_system, resmut_system).run_if(|| true));
|
||||
|
||||
@ -51,6 +51,7 @@ pub(super) trait ScheduleBuildPassObj: Send + Sync + Debug {
|
||||
);
|
||||
fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap<Box<dyn Any>>);
|
||||
}
|
||||
|
||||
impl<T: ScheduleBuildPass> ScheduleBuildPassObj for T {
|
||||
fn build(
|
||||
&mut self,
|
||||
|
||||
@ -235,6 +235,7 @@ pub enum Chain {
|
||||
/// will be added between the successive elements.
|
||||
Chained(TypeIdMap<Box<dyn Any>>),
|
||||
}
|
||||
|
||||
impl Chain {
|
||||
/// Specify that the systems must be chained.
|
||||
pub fn set_chained(&mut self) {
|
||||
|
||||
@ -210,6 +210,7 @@ unsafe impl<R: Relationship, L: SpawnableList<R> + Send + Sync + 'static> Bundle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Relationship, L: SpawnableList<R>> DynamicBundle for SpawnRelatedBundle<R, L> {
|
||||
type Effect = Self;
|
||||
|
||||
|
||||
@ -223,6 +223,7 @@ pub fn trigger(event: impl Event) -> impl Command {
|
||||
}
|
||||
|
||||
/// A [`Command`] that sends an [`EntityEvent`] for the given targets.
|
||||
#[track_caller]
|
||||
pub fn trigger_targets(
|
||||
event: impl EntityEvent,
|
||||
targets: impl TriggerTargets + Send + Sync + 'static,
|
||||
|
||||
@ -2392,7 +2392,7 @@ mod tests {
|
||||
.spawn((W(1u32), W(2u64)))
|
||||
.id();
|
||||
command_queue.apply(&mut world);
|
||||
assert_eq!(world.entities().count_constructed(), 1);
|
||||
assert_eq!(world.entity_count(), 1);
|
||||
let results = world
|
||||
.query::<(&W<u32>, &W<u64>)>()
|
||||
.iter(&world)
|
||||
|
||||
@ -634,7 +634,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."]
|
||||
#[should_panic]
|
||||
fn any_of_with_mut_and_ref() {
|
||||
fn sys(_: Query<AnyOf<(&mut A, &A)>>) {}
|
||||
let mut world = World::default();
|
||||
@ -642,7 +642,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."]
|
||||
#[should_panic]
|
||||
fn any_of_with_ref_and_mut() {
|
||||
fn sys(_: Query<AnyOf<(&A, &mut A)>>) {}
|
||||
let mut world = World::default();
|
||||
@ -650,7 +650,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "&bevy_ecs::system::tests::A conflicts with a previous access in this query."]
|
||||
#[should_panic]
|
||||
fn any_of_with_mut_and_option() {
|
||||
fn sys(_: Query<AnyOf<(&mut A, Option<&A>)>>) {}
|
||||
let mut world = World::default();
|
||||
@ -680,7 +680,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "&mut bevy_ecs::system::tests::A conflicts with a previous access in this query."]
|
||||
#[should_panic]
|
||||
fn any_of_with_conflicting() {
|
||||
fn sys(_: Query<AnyOf<(&mut A, &mut A)>>) {}
|
||||
let mut world = World::default();
|
||||
@ -1629,54 +1629,42 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_world_and_entity_mut_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
||||
)]
|
||||
#[should_panic]
|
||||
fn assert_world_and_entity_mut_system_does_conflict_first() {
|
||||
fn system(_query: &World, _q2: Query<EntityMut>) {}
|
||||
super::assert_system_does_not_conflict(system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "&World conflicts with a previous mutable system parameter. Allowing this would break Rust's mutability rules"
|
||||
)]
|
||||
#[should_panic]
|
||||
fn assert_world_and_entity_mut_system_does_conflict_second() {
|
||||
fn system(_: Query<EntityMut>, _: &World) {}
|
||||
super::assert_system_does_not_conflict(system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_ref_and_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
||||
)]
|
||||
#[should_panic]
|
||||
fn assert_entity_ref_and_entity_mut_system_does_conflict() {
|
||||
fn system(_query: Query<EntityRef>, _q2: Query<EntityMut>) {}
|
||||
super::assert_system_does_not_conflict(system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "error[B0001]: Query<EntityMut, ()> in system bevy_ecs::system::tests::assert_entity_mut_system_does_conflict::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
||||
)]
|
||||
#[should_panic]
|
||||
fn assert_entity_mut_system_does_conflict() {
|
||||
fn system(_query: Query<EntityMut>, _q2: Query<EntityMut>) {}
|
||||
super::assert_system_does_not_conflict(system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "error[B0001]: Query<EntityRef, ()> in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_first::system accesses component(s) in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevy.org/learn/errors/b0001"
|
||||
)]
|
||||
#[should_panic]
|
||||
fn assert_deferred_world_and_entity_ref_system_does_conflict_first() {
|
||||
fn system(_world: DeferredWorld, _query: Query<EntityRef>) {}
|
||||
super::assert_system_does_not_conflict(system);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "DeferredWorld in system bevy_ecs::system::tests::assert_deferred_world_and_entity_ref_system_does_conflict_second::system conflicts with a previous access."
|
||||
)]
|
||||
#[should_panic]
|
||||
fn assert_deferred_world_and_entity_ref_system_does_conflict_second() {
|
||||
fn system(_query: Query<EntityRef>, _world: DeferredWorld) {}
|
||||
super::assert_system_does_not_conflict(system);
|
||||
|
||||
@ -73,6 +73,7 @@ where
|
||||
InfallibleObserverWrapper::new(IntoSystem::into_system(this))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E, B, M, S> IntoObserverSystem<E, B, (Never, M), Result> for S
|
||||
where
|
||||
S: IntoSystem<On<'static, E, B>, Never, M> + Send + 'static,
|
||||
|
||||
@ -410,7 +410,6 @@ pub enum RunSystemError {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::prelude::*;
|
||||
use alloc::string::ToString;
|
||||
|
||||
#[test]
|
||||
fn run_system_once() {
|
||||
@ -483,7 +482,5 @@ mod tests {
|
||||
let result = world.run_system_once(system);
|
||||
|
||||
assert!(matches!(result, Err(RunSystemError::InvalidParams { .. })));
|
||||
let expected = "System bevy_ecs::system::system::tests::run_system_once_invalid_params::system did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.";
|
||||
assert_eq!(expected, result.unwrap_err().to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@ -85,6 +85,7 @@ impl ExclusiveSystemParam for SystemName {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(feature = "trace")]
|
||||
mod tests {
|
||||
use crate::{
|
||||
system::{IntoSystem, RunSystemOnce, SystemName},
|
||||
|
||||
@ -151,6 +151,7 @@ use variadics_please::{all_tuples, all_tuples_enumerated};
|
||||
/// let mut world = World::new();
|
||||
/// let err = world.run_system_cached(|param: MyParam| {}).unwrap_err();
|
||||
/// let expected = "Parameter `MyParam::foo` failed validation: Custom Message";
|
||||
/// # #[cfg(feature="Trace")] // Without debug_utils/debug enabled MyParam::foo is stripped and breaks the assert
|
||||
/// assert!(err.to_string().contains(expected));
|
||||
/// ```
|
||||
///
|
||||
@ -1399,6 +1400,7 @@ impl<'w, T> Deref for NonSend<'w, T> {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> From<NonSendMut<'a, T>> for NonSend<'a, T> {
|
||||
fn from(nsm: NonSendMut<'a, T>) -> Self {
|
||||
Self {
|
||||
@ -3105,7 +3107,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_resource_error::res_system`: Parameter `Res<MissingResource>` failed validation: Resource does not exist"]
|
||||
#[should_panic]
|
||||
fn missing_resource_error() {
|
||||
#[derive(Resource)]
|
||||
pub struct MissingResource;
|
||||
@ -3119,7 +3121,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "Encountered an error in system `bevy_ecs::system::system_param::tests::missing_event_error::event_system`: Parameter `EventReader<MissingEvent>::events` failed validation: BufferedEvent not initialized"]
|
||||
#[should_panic]
|
||||
fn missing_event_error() {
|
||||
use crate::prelude::{BufferedEvent, EventReader};
|
||||
|
||||
|
||||
@ -913,7 +913,6 @@ mod tests {
|
||||
#[test]
|
||||
fn run_system_invalid_params() {
|
||||
use crate::system::RegisteredSystemError;
|
||||
use alloc::{format, string::ToString};
|
||||
|
||||
struct T;
|
||||
impl Resource for T {}
|
||||
@ -928,8 +927,6 @@ mod tests {
|
||||
result,
|
||||
Err(RegisteredSystemError::InvalidParams { .. })
|
||||
));
|
||||
let expected = format!("System {id:?} did not run due to failed parameter validation: Parameter `Res<T>` failed validation: Resource does not exist\nIf this is an expected state, wrap the parameter in `Option<T>` and handle `None` when it happens, or wrap the parameter in `When<T>` to skip the system when it happens.");
|
||||
assert_eq!(expected, result.unwrap_err().to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -420,12 +420,12 @@ mod test {
|
||||
let mut world = World::new();
|
||||
queue.apply(&mut world);
|
||||
|
||||
assert_eq!(world.entities().count_constructed(), 2);
|
||||
assert_eq!(world.entity_count(), 2);
|
||||
|
||||
// The previous call to `apply` cleared the queue.
|
||||
// This call should do nothing.
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(world.entities().count_constructed(), 2);
|
||||
assert_eq!(world.entity_count(), 2);
|
||||
}
|
||||
|
||||
#[expect(
|
||||
@ -459,7 +459,7 @@ mod test {
|
||||
queue.push(SpawnCommand);
|
||||
queue.push(SpawnCommand);
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(world.entities().count_constructed(), 3);
|
||||
assert_eq!(world.entity_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -102,9 +102,11 @@ impl<'w> DeferredWorld<'w> {
|
||||
/// If you do not need to ensure the above hooks are triggered, and your component
|
||||
/// is mutable, prefer using [`get_mut`](DeferredWorld::get_mut).
|
||||
#[inline]
|
||||
pub(crate) fn modify_component<T: Component, R>(
|
||||
#[track_caller]
|
||||
pub(crate) fn modify_component_with_relationship_hook_mode<T: Component, R>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
relationship_hook_mode: RelationshipHookMode,
|
||||
f: impl FnOnce(&mut T) -> R,
|
||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||
// If the component is not registered, then it doesn't exist on this entity, so no action required.
|
||||
@ -112,12 +114,17 @@ impl<'w> DeferredWorld<'w> {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
self.modify_component_by_id(entity, component_id, move |component| {
|
||||
self.modify_component_by_id_with_relationship_hook_mode(
|
||||
entity,
|
||||
component_id,
|
||||
relationship_hook_mode,
|
||||
move |component| {
|
||||
// SAFETY: component matches the component_id collected in the above line
|
||||
let mut component = unsafe { component.with_type::<T>() };
|
||||
|
||||
f(&mut component)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Temporarily removes a [`Component`] identified by the provided
|
||||
@ -132,13 +139,15 @@ impl<'w> DeferredWorld<'w> {
|
||||
/// If you do not need to ensure the above hooks are triggered, and your component
|
||||
/// is mutable, prefer using [`get_mut_by_id`](DeferredWorld::get_mut_by_id).
|
||||
///
|
||||
/// You should prefer the typed [`modify_component`](DeferredWorld::modify_component)
|
||||
/// You should prefer the typed [`modify_component_with_relationship_hook_mode`](DeferredWorld::modify_component_with_relationship_hook_mode)
|
||||
/// whenever possible.
|
||||
#[inline]
|
||||
pub(crate) fn modify_component_by_id<R>(
|
||||
#[track_caller]
|
||||
pub(crate) fn modify_component_by_id_with_relationship_hook_mode<R>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
component_id: ComponentId,
|
||||
relationship_hook_mode: RelationshipHookMode,
|
||||
f: impl for<'a> FnOnce(MutUntyped<'a>) -> R,
|
||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||
let entity_cell = self.get_entity_mut(entity)?;
|
||||
@ -162,7 +171,7 @@ impl<'w> DeferredWorld<'w> {
|
||||
entity,
|
||||
[component_id].into_iter(),
|
||||
MaybeLocation::caller(),
|
||||
RelationshipHookMode::Run,
|
||||
relationship_hook_mode,
|
||||
);
|
||||
if archetype.has_replace_observer() {
|
||||
self.trigger_observers(
|
||||
@ -202,7 +211,7 @@ impl<'w> DeferredWorld<'w> {
|
||||
entity,
|
||||
[component_id].into_iter(),
|
||||
MaybeLocation::caller(),
|
||||
RelationshipHookMode::Run,
|
||||
relationship_hook_mode,
|
||||
);
|
||||
if archetype.has_insert_observer() {
|
||||
self.trigger_observers(
|
||||
|
||||
@ -219,6 +219,14 @@ impl World {
|
||||
&mut self.entities
|
||||
}
|
||||
|
||||
/// Retrieves the number of [`Entities`] in the world.
|
||||
///
|
||||
/// This is helpful as a diagnostic, but it can also be used effectively in tests.
|
||||
#[inline]
|
||||
pub fn entity_count(&self) -> u32 {
|
||||
self.entities.len()
|
||||
}
|
||||
|
||||
/// Retrieves this world's [`Archetypes`] collection.
|
||||
#[inline]
|
||||
pub fn archetypes(&self) -> &Archetypes {
|
||||
@ -1426,6 +1434,7 @@ impl World {
|
||||
/// # assert_eq!(world.get::<Foo>(entity), Some(&Foo(true)));
|
||||
/// ```
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn modify_component<T: Component, R>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
@ -1433,7 +1442,11 @@ impl World {
|
||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||
let mut world = DeferredWorld::from(&mut *self);
|
||||
|
||||
let result = world.modify_component(entity, f)?;
|
||||
let result = world.modify_component_with_relationship_hook_mode(
|
||||
entity,
|
||||
RelationshipHookMode::Run,
|
||||
f,
|
||||
)?;
|
||||
|
||||
self.flush();
|
||||
Ok(result)
|
||||
@ -1454,6 +1467,7 @@ impl World {
|
||||
/// You should prefer the typed [`modify_component`](World::modify_component)
|
||||
/// whenever possible.
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
pub fn modify_component_by_id<R>(
|
||||
&mut self,
|
||||
entity: Entity,
|
||||
@ -1462,7 +1476,12 @@ impl World {
|
||||
) -> Result<Option<R>, EntityMutableFetchError> {
|
||||
let mut world = DeferredWorld::from(&mut *self);
|
||||
|
||||
let result = world.modify_component_by_id(entity, component_id, f)?;
|
||||
let result = world.modify_component_by_id_with_relationship_hook_mode(
|
||||
entity,
|
||||
component_id,
|
||||
RelationshipHookMode::Run,
|
||||
f,
|
||||
)?;
|
||||
|
||||
self.flush();
|
||||
Ok(result)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_encase_derive"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Bevy derive macro for encase"
|
||||
homepage = "https://bevy.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
bevy_macro_utils = { path = "../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
encase_derive_impl = "0.10"
|
||||
|
||||
[lints]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gilrs"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Gamepad system made using Gilrs for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -10,12 +10,12 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
|
||||
@ -47,6 +47,7 @@ pub(crate) struct Gilrs {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
cell: SyncCell<gilrs::Gilrs>,
|
||||
}
|
||||
|
||||
impl Gilrs {
|
||||
#[inline]
|
||||
pub fn with(&mut self, f: impl FnOnce(&mut gilrs::Gilrs)) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gizmos"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides gizmos for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -15,21 +15,21 @@ bevy_render = ["dep:bevy_render", "bevy_core_pipeline"]
|
||||
|
||||
[dependencies]
|
||||
# Bevy
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev", optional = true }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.16.0-dev", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev", optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_gizmos_macros = { path = "macros", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev", optional = true }
|
||||
bevy_sprite = { path = "../bevy_sprite", version = "0.17.0-dev", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev", optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_gizmos_macros = { path = "macros", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
bytemuck = "1.0"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gizmos_macros"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Derive implementations for bevy_gizmos"
|
||||
homepage = "https://bevy.org"
|
||||
@ -13,7 +13,7 @@ proc-macro = true
|
||||
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
||||
@ -172,6 +172,7 @@ where
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<Config, Clear> GizmoBuffer<Config, Clear>
|
||||
where
|
||||
Config: GizmoConfigGroup,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_gltf"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Bevy Engine GLTF loading"
|
||||
homepage = "https://bevy.org"
|
||||
@ -18,25 +18,25 @@ pbr_specular_textures = ["bevy_pbr/pbr_specular_textures"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_animation = { path = "../bevy_animation", version = "0.16.0-dev", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_scene = { path = "../bevy_scene", version = "0.16.0-dev", features = [
|
||||
bevy_animation = { path = "../bevy_animation", version = "0.17.0-dev", optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||
bevy_pbr = { path = "../bevy_pbr", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev", features = [
|
||||
"bevy_render",
|
||||
] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
@ -66,7 +66,7 @@ smallvec = "1.11"
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_image"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides image types for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -40,17 +40,17 @@ zstd = ["ruzstd"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [
|
||||
"serialize",
|
||||
"wgpu-types",
|
||||
] }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
@ -77,7 +77,7 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
half = { version = "2.4.1" }
|
||||
|
||||
[dev-dependencies]
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -34,6 +34,7 @@ pub struct TextureAtlasSources {
|
||||
/// Maps from a specific image handle to the index in `textures` where they can be found.
|
||||
pub texture_ids: HashMap<AssetId<Image>, usize>,
|
||||
}
|
||||
|
||||
impl TextureAtlasSources {
|
||||
/// Retrieves the texture *section* index of the given `texture` handle.
|
||||
pub fn texture_index(&self, texture: impl Into<AssetId<Image>>) -> Option<usize> {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_input"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides input functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -60,14 +60,14 @@ libm = ["bevy_math/libm"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
"glam",
|
||||
], default-features = false, optional = true }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false }
|
||||
|
||||
# other
|
||||
serde = { version = "1", features = [
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_input_focus"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Keyboard focus management"
|
||||
homepage = "https://bevy.org"
|
||||
@ -60,12 +60,13 @@ libm = ["bevy_math/libm", "bevy_window/libm"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev", default-features = false }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev", default-features = false }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
"glam",
|
||||
], default-features = false, optional = true }
|
||||
|
||||
|
||||
@ -147,6 +147,15 @@ pub struct FocusedInput<E: BufferedEvent + Clone> {
|
||||
window: Entity,
|
||||
}
|
||||
|
||||
/// An event which is used to set input focus. Trigger this on an entity, and it will bubble
|
||||
/// until it finds a focusable entity, and then set focus to it.
|
||||
#[derive(Clone, Event, EntityEvent)]
|
||||
#[entity_event(traversal = WindowTraversal, auto_propagate)]
|
||||
pub struct AcquireFocus {
|
||||
/// The primary window entity.
|
||||
window: Entity,
|
||||
}
|
||||
|
||||
#[derive(QueryData)]
|
||||
/// These are for accessing components defined on the targeted entity
|
||||
pub struct WindowTraversal {
|
||||
@ -172,6 +181,24 @@ impl<E: BufferedEvent + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
|
||||
}
|
||||
}
|
||||
|
||||
impl Traversal<AcquireFocus> for WindowTraversal {
|
||||
fn traverse(item: Self::Item<'_, '_>, event: &AcquireFocus) -> Option<Entity> {
|
||||
let WindowTraversalItem { child_of, window } = item;
|
||||
|
||||
// Send event to parent, if it has one.
|
||||
if let Some(child_of) = child_of {
|
||||
return Some(child_of.parent());
|
||||
};
|
||||
|
||||
// Otherwise, send it to the window entity (unless this is a window entity).
|
||||
if window.is_none() {
|
||||
return Some(event.window);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin which sets up systems for dispatching bubbling keyboard and gamepad button events to the focused entity.
|
||||
///
|
||||
/// To add bubbling to your own input events, add the [`dispatch_focused_input::<MyEvent>`](dispatch_focused_input) system to your app,
|
||||
|
||||
@ -38,11 +38,12 @@ use bevy_input::{
|
||||
keyboard::{KeyCode, KeyboardInput},
|
||||
ButtonInput, ButtonState,
|
||||
};
|
||||
use bevy_window::PrimaryWindow;
|
||||
use bevy_picking::events::{Pointer, Press};
|
||||
use bevy_window::{PrimaryWindow, Window};
|
||||
use log::warn;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::{FocusedInput, InputFocus, InputFocusVisible};
|
||||
use crate::{AcquireFocus, FocusedInput, InputFocus, InputFocusVisible};
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use {
|
||||
@ -312,6 +313,31 @@ impl TabNavigation<'_, '_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Observer which sets focus to the nearest ancestor that has tab index, using bubbling.
|
||||
pub(crate) fn acquire_focus(
|
||||
mut ev: On<AcquireFocus>,
|
||||
focusable: Query<(), With<TabIndex>>,
|
||||
windows: Query<(), With<Window>>,
|
||||
mut focus: ResMut<InputFocus>,
|
||||
) {
|
||||
// If the entity has a TabIndex
|
||||
if focusable.contains(ev.target()) {
|
||||
// Stop and focus it
|
||||
ev.propagate(false);
|
||||
// Don't mutate unless we need to, for change detection
|
||||
if focus.0 != Some(ev.target()) {
|
||||
focus.0 = Some(ev.target());
|
||||
}
|
||||
} else if windows.contains(ev.target()) {
|
||||
// Stop and clear focus
|
||||
ev.propagate(false);
|
||||
// Don't mutate unless we need to, for change detection
|
||||
if focus.0.is_some() {
|
||||
focus.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin for navigating between focusable entities using keyboard input.
|
||||
pub struct TabNavigationPlugin;
|
||||
|
||||
@ -321,6 +347,8 @@ impl Plugin for TabNavigationPlugin {
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
app.register_type::<TabIndex>().register_type::<TabGroup>();
|
||||
app.add_observer(acquire_focus);
|
||||
app.add_observer(click_to_focus);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,6 +358,30 @@ fn setup_tab_navigation(mut commands: Commands, window: Query<Entity, With<Prima
|
||||
}
|
||||
}
|
||||
|
||||
fn click_to_focus(
|
||||
ev: On<Pointer<Press>>,
|
||||
mut focus_visible: ResMut<InputFocusVisible>,
|
||||
windows: Query<Entity, With<PrimaryWindow>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// Because `Pointer` is a bubbling event, we don't want to trigger an `AcquireFocus` event
|
||||
// for every ancestor, but only for the original entity. Also, users may want to stop
|
||||
// propagation on the pointer event at some point along the bubbling chain, so we need our
|
||||
// own dedicated event whose propagation we can control.
|
||||
if ev.target() == ev.original_target() {
|
||||
// Clicking hides focus
|
||||
if focus_visible.0 {
|
||||
focus_visible.0 = false;
|
||||
}
|
||||
// Search for a focusable parent entity, defaulting to window if none.
|
||||
if let Ok(window) = windows.single() {
|
||||
commands
|
||||
.entity(ev.target())
|
||||
.trigger(AcquireFocus { window });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Observer function which handles tab navigation.
|
||||
///
|
||||
/// This observer responds to [`KeyCode::Tab`] events and Shift+Tab events,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_internal"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "An internal Bevy crate used to facilitate optional dynamic linking via the 'dynamic_linking' feature"
|
||||
homepage = "https://bevy.org"
|
||||
@ -350,80 +350,80 @@ debug = ["bevy_utils/debug"]
|
||||
|
||||
[dependencies]
|
||||
# bevy (no_std)
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev", default-features = false }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev", default-features = false }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
"nostd-libm",
|
||||
] }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_ptr = { path = "../bevy_ptr", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"smallvec",
|
||||
] }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy-support",
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||
|
||||
# bevy (std required)
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev", optional = true }
|
||||
bevy_log = { path = "../bevy_log", version = "0.17.0-dev", optional = true }
|
||||
|
||||
# bevy (optional)
|
||||
bevy_a11y = { path = "../bevy_a11y", optional = true, version = "0.16.0-dev", features = [
|
||||
bevy_a11y = { path = "../bevy_a11y", optional = true, version = "0.17.0-dev", features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.16.0-dev" }
|
||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_animation = { path = "../bevy_animation", optional = true, version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", optional = true, version = "0.17.0-dev" }
|
||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.16.0-dev" }
|
||||
bevy_core_widgets = { path = "../bevy_core_widgets", optional = true, version = "0.16.0-dev" }
|
||||
bevy_anti_aliasing = { path = "../bevy_anti_aliasing", optional = true, version = "0.16.0-dev" }
|
||||
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.16.0-dev" }
|
||||
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.16.0-dev" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.16.0-dev", default-features = false }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", optional = true, version = "0.16.0-dev" }
|
||||
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.17.0-dev" }
|
||||
bevy_core_widgets = { path = "../bevy_core_widgets", optional = true, version = "0.17.0-dev" }
|
||||
bevy_anti_aliasing = { path = "../bevy_anti_aliasing", optional = true, version = "0.17.0-dev" }
|
||||
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.17.0-dev" }
|
||||
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.17.0-dev" }
|
||||
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.17.0-dev", default-features = false }
|
||||
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", optional = true, version = "0.17.0-dev" }
|
||||
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.16.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.16.0-dev" }
|
||||
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.16.0-dev" }
|
||||
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.16.0-dev" }
|
||||
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.16.0-dev" }
|
||||
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.16.0-dev" }
|
||||
bevy_state = { path = "../bevy_state", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.17.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.17.0-dev" }
|
||||
bevy_remote = { path = "../bevy_remote", optional = true, version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", optional = true, version = "0.17.0-dev" }
|
||||
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.17.0-dev" }
|
||||
bevy_solari = { path = "../bevy_solari", optional = true, version = "0.17.0-dev" }
|
||||
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.17.0-dev" }
|
||||
bevy_state = { path = "../bevy_state", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_app",
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_text = { path = "../bevy_text", optional = true, version = "0.16.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", optional = true, version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_text = { path = "../bevy_text", optional = true, version = "0.17.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", optional = true, version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.16.0-dev", default-features = false }
|
||||
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.17.0-dev", default-features = false }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_log"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides logging for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -14,10 +14,10 @@ trace_tracy_memory = ["dep:tracy-client"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
tracing-subscriber = { version = "0.3.1", features = [
|
||||
@ -40,7 +40,7 @@ android_log-sys = "0.3.0"
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
tracing-wasm = "0.2.1"
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_macro_utils"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "A collection of utils for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_math"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides math functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -25,7 +25,7 @@ approx = { version = "0.5", default-features = false, optional = true }
|
||||
rand = { version = "0.8", default-features = false, optional = true }
|
||||
rand_distr = { version = "0.4.3", optional = true }
|
||||
smallvec = { version = "1.11" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"glam",
|
||||
], optional = true }
|
||||
variadics_please = "1.1"
|
||||
|
||||
@ -1347,6 +1347,7 @@ pub struct RationalSegment<P: VectorSpace> {
|
||||
/// The width of the domain of this segment.
|
||||
pub knot_span: f32,
|
||||
}
|
||||
|
||||
impl<P: VectorSpace<Scalar = f32>> RationalSegment<P> {
|
||||
/// Instantaneous position of a point at parametric value `t` in `[0, 1]`.
|
||||
#[inline]
|
||||
|
||||
@ -35,6 +35,7 @@ pub struct Circle {
|
||||
/// The radius of the circle
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
impl Primitive2d for Circle {}
|
||||
|
||||
impl Default for Circle {
|
||||
@ -124,6 +125,7 @@ pub struct Arc2d {
|
||||
/// Half the angle defining the arc
|
||||
pub half_angle: f32,
|
||||
}
|
||||
|
||||
impl Primitive2d for Arc2d {}
|
||||
|
||||
impl Default for Arc2d {
|
||||
@ -290,6 +292,7 @@ pub struct CircularSector {
|
||||
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
|
||||
pub arc: Arc2d,
|
||||
}
|
||||
|
||||
impl Primitive2d for CircularSector {}
|
||||
|
||||
impl Default for CircularSector {
|
||||
@ -433,6 +436,7 @@ pub struct CircularSegment {
|
||||
#[cfg_attr(all(feature = "serialize", feature = "alloc"), serde(flatten))]
|
||||
pub arc: Arc2d,
|
||||
}
|
||||
|
||||
impl Primitive2d for CircularSegment {}
|
||||
|
||||
impl Default for CircularSegment {
|
||||
@ -453,6 +457,7 @@ impl Measured2d for CircularSegment {
|
||||
self.chord_length() + self.arc_length()
|
||||
}
|
||||
}
|
||||
|
||||
impl CircularSegment {
|
||||
/// Create a new [`CircularSegment`] from a `radius`, and an `angle`
|
||||
#[inline(always)]
|
||||
@ -788,6 +793,7 @@ pub struct Ellipse {
|
||||
/// This corresponds to the two perpendicular radii defining the ellipse.
|
||||
pub half_size: Vec2,
|
||||
}
|
||||
|
||||
impl Primitive2d for Ellipse {}
|
||||
|
||||
impl Default for Ellipse {
|
||||
@ -939,6 +945,7 @@ pub struct Annulus {
|
||||
/// The outer circle of the annulus
|
||||
pub outer_circle: Circle,
|
||||
}
|
||||
|
||||
impl Primitive2d for Annulus {}
|
||||
|
||||
impl Default for Annulus {
|
||||
@ -1036,6 +1043,7 @@ pub struct Rhombus {
|
||||
/// Size of the horizontal and vertical diagonals of the rhombus
|
||||
pub half_diagonals: Vec2,
|
||||
}
|
||||
|
||||
impl Primitive2d for Rhombus {}
|
||||
|
||||
impl Default for Rhombus {
|
||||
@ -1171,6 +1179,7 @@ pub struct Plane2d {
|
||||
/// The normal of the plane. The plane will be placed perpendicular to this direction
|
||||
pub normal: Dir2,
|
||||
}
|
||||
|
||||
impl Primitive2d for Plane2d {}
|
||||
|
||||
impl Default for Plane2d {
|
||||
@ -1213,6 +1222,7 @@ pub struct Line2d {
|
||||
/// and its opposite direction
|
||||
pub direction: Dir2,
|
||||
}
|
||||
|
||||
impl Primitive2d for Line2d {}
|
||||
|
||||
/// A line segment defined by two endpoints in 2D space.
|
||||
@ -1232,6 +1242,7 @@ pub struct Segment2d {
|
||||
/// The endpoints of the line segment.
|
||||
pub vertices: [Vec2; 2],
|
||||
}
|
||||
|
||||
impl Primitive2d for Segment2d {}
|
||||
|
||||
impl Segment2d {
|
||||
@ -1504,6 +1515,7 @@ pub struct Polyline2d<const N: usize> {
|
||||
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||
pub vertices: [Vec2; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Primitive2d for Polyline2d<N> {}
|
||||
|
||||
impl<const N: usize> FromIterator<Vec2> for Polyline2d<N> {
|
||||
@ -1573,6 +1585,7 @@ pub struct Triangle2d {
|
||||
/// The vertices of the triangle
|
||||
pub vertices: [Vec2; 3],
|
||||
}
|
||||
|
||||
impl Primitive2d for Triangle2d {}
|
||||
|
||||
impl Default for Triangle2d {
|
||||
@ -1745,6 +1758,7 @@ pub struct Rectangle {
|
||||
/// Half of the width and height of the rectangle
|
||||
pub half_size: Vec2,
|
||||
}
|
||||
|
||||
impl Primitive2d for Rectangle {}
|
||||
|
||||
impl Default for Rectangle {
|
||||
@ -1838,6 +1852,7 @@ pub struct Polygon<const N: usize> {
|
||||
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||
pub vertices: [Vec2; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Primitive2d for Polygon<N> {}
|
||||
|
||||
impl<const N: usize> FromIterator<Vec2> for Polygon<N> {
|
||||
@ -1892,6 +1907,7 @@ pub struct ConvexPolygon<const N: usize> {
|
||||
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||
vertices: [Vec2; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Primitive2d for ConvexPolygon<N> {}
|
||||
|
||||
/// An error that happens when creating a [`ConvexPolygon`].
|
||||
@ -2013,6 +2029,7 @@ pub struct RegularPolygon {
|
||||
/// The number of sides
|
||||
pub sides: u32,
|
||||
}
|
||||
|
||||
impl Primitive2d for RegularPolygon {}
|
||||
|
||||
impl Default for RegularPolygon {
|
||||
@ -2160,6 +2177,7 @@ pub struct Capsule2d {
|
||||
/// Half the height of the capsule, excluding the semicircles
|
||||
pub half_length: f32,
|
||||
}
|
||||
|
||||
impl Primitive2d for Capsule2d {}
|
||||
|
||||
impl Default for Capsule2d {
|
||||
|
||||
@ -31,6 +31,7 @@ pub struct Sphere {
|
||||
/// The radius of the sphere
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
impl Primitive3d for Sphere {}
|
||||
|
||||
impl Default for Sphere {
|
||||
@ -105,6 +106,7 @@ pub struct Plane3d {
|
||||
/// Half of the width and height of the plane
|
||||
pub half_size: Vec2,
|
||||
}
|
||||
|
||||
impl Primitive3d for Plane3d {}
|
||||
|
||||
impl Default for Plane3d {
|
||||
@ -175,6 +177,7 @@ pub struct InfinitePlane3d {
|
||||
/// The normal of the plane. The plane will be placed perpendicular to this direction
|
||||
pub normal: Dir3,
|
||||
}
|
||||
|
||||
impl Primitive3d for InfinitePlane3d {}
|
||||
|
||||
impl Default for InfinitePlane3d {
|
||||
@ -351,6 +354,7 @@ pub struct Line3d {
|
||||
/// The direction of the line
|
||||
pub direction: Dir3,
|
||||
}
|
||||
|
||||
impl Primitive3d for Line3d {}
|
||||
|
||||
/// A line segment defined by two endpoints in 3D space.
|
||||
@ -370,6 +374,7 @@ pub struct Segment3d {
|
||||
/// The endpoints of the line segment.
|
||||
pub vertices: [Vec3; 2],
|
||||
}
|
||||
|
||||
impl Primitive3d for Segment3d {}
|
||||
|
||||
impl Segment3d {
|
||||
@ -578,6 +583,7 @@ pub struct Polyline3d<const N: usize> {
|
||||
#[cfg_attr(feature = "serialize", serde(with = "super::serde::array"))]
|
||||
pub vertices: [Vec3; N],
|
||||
}
|
||||
|
||||
impl<const N: usize> Primitive3d for Polyline3d<N> {}
|
||||
|
||||
impl<const N: usize> FromIterator<Vec3> for Polyline3d<N> {
|
||||
@ -648,6 +654,7 @@ pub struct Cuboid {
|
||||
/// Half of the width, height and depth of the cuboid
|
||||
pub half_size: Vec3,
|
||||
}
|
||||
|
||||
impl Primitive3d for Cuboid {}
|
||||
|
||||
impl Default for Cuboid {
|
||||
@ -742,6 +749,7 @@ pub struct Cylinder {
|
||||
/// The half height of the cylinder
|
||||
pub half_height: f32,
|
||||
}
|
||||
|
||||
impl Primitive3d for Cylinder {}
|
||||
|
||||
impl Default for Cylinder {
|
||||
@ -820,6 +828,7 @@ pub struct Capsule3d {
|
||||
/// Half the height of the capsule, excluding the hemispheres
|
||||
pub half_length: f32,
|
||||
}
|
||||
|
||||
impl Primitive3d for Capsule3d {}
|
||||
|
||||
impl Default for Capsule3d {
|
||||
@ -890,6 +899,7 @@ pub struct Cone {
|
||||
/// The height of the cone
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl Primitive3d for Cone {}
|
||||
|
||||
impl Default for Cone {
|
||||
@ -974,6 +984,7 @@ pub struct ConicalFrustum {
|
||||
/// The height of the frustum
|
||||
pub height: f32,
|
||||
}
|
||||
|
||||
impl Primitive3d for ConicalFrustum {}
|
||||
|
||||
impl Default for ConicalFrustum {
|
||||
@ -1030,6 +1041,7 @@ pub struct Torus {
|
||||
#[doc(alias = "radius_of_revolution")]
|
||||
pub major_radius: f32,
|
||||
}
|
||||
|
||||
impl Primitive3d for Torus {}
|
||||
|
||||
impl Default for Torus {
|
||||
@ -1326,6 +1338,7 @@ pub struct Tetrahedron {
|
||||
/// The vertices of the tetrahedron.
|
||||
pub vertices: [Vec3; 4],
|
||||
}
|
||||
|
||||
impl Primitive3d for Tetrahedron {}
|
||||
|
||||
impl Default for Tetrahedron {
|
||||
@ -1433,6 +1446,7 @@ pub struct Extrusion<T: Primitive2d> {
|
||||
/// Half of the depth of the extrusion
|
||||
pub half_depth: f32,
|
||||
}
|
||||
|
||||
impl<T: Primitive2d> Primitive3d for Extrusion<T> {}
|
||||
|
||||
impl<T: Primitive2d> Extrusion<T> {
|
||||
|
||||
@ -34,6 +34,7 @@ struct SweepLineEvent {
|
||||
/// Type of the vertex (left or right)
|
||||
endpoint: Endpoint,
|
||||
}
|
||||
|
||||
impl SweepLineEvent {
|
||||
#[cfg_attr(
|
||||
not(feature = "alloc"),
|
||||
@ -46,17 +47,21 @@ impl SweepLineEvent {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for SweepLineEvent {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.position() == other.position()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SweepLineEvent {}
|
||||
|
||||
impl PartialOrd for SweepLineEvent {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for SweepLineEvent {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
xy_order(self.position(), other.position())
|
||||
@ -129,11 +134,13 @@ struct Segment {
|
||||
left: Vec2,
|
||||
right: Vec2,
|
||||
}
|
||||
|
||||
impl PartialEq for Segment {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.edge_index == other.edge_index
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Segment {}
|
||||
|
||||
impl PartialOrd for Segment {
|
||||
@ -141,6 +148,7 @@ impl PartialOrd for Segment {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Segment {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.left
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_mesh"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides mesh types for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -10,16 +10,16 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
@ -163,6 +163,7 @@ impl Iterator for IndicesIter<'_> {
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for IndicesIter<'a> {}
|
||||
|
||||
impl<'a> FusedIterator for IndicesIter<'a> {}
|
||||
|
||||
impl From<&Indices> for IndexFormat {
|
||||
|
||||
@ -117,6 +117,7 @@ pub struct MorphWeights {
|
||||
/// The first mesh primitive assigned to these weights
|
||||
first_mesh: Option<Handle<Mesh>>,
|
||||
}
|
||||
|
||||
impl MorphWeights {
|
||||
pub fn new(
|
||||
weights: Vec<f32>,
|
||||
@ -160,6 +161,7 @@ impl MorphWeights {
|
||||
pub struct MeshMorphWeights {
|
||||
weights: Vec<f32>,
|
||||
}
|
||||
|
||||
impl MeshMorphWeights {
|
||||
pub fn new(weights: Vec<f32>) -> Result<Self, MorphBuildError> {
|
||||
if weights.len() > MAX_MORPH_WEIGHTS {
|
||||
@ -198,6 +200,7 @@ pub struct MorphAttributes {
|
||||
/// animated, as the `w` component is the sign and cannot be animated.
|
||||
pub tangent: Vec3,
|
||||
}
|
||||
|
||||
impl From<[Vec3; 3]> for MorphAttributes {
|
||||
fn from([position, normal, tangent]: [Vec3; 3]) -> Self {
|
||||
MorphAttributes {
|
||||
@ -207,6 +210,7 @@ impl From<[Vec3; 3]> for MorphAttributes {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MorphAttributes {
|
||||
/// How many components `MorphAttributes` has.
|
||||
///
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_mikktspace"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
authors = [
|
||||
"Benjamin Wasty <benny.wasty@gmail.com>",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_pbr"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Adds PBR rendering to Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -31,22 +31,22 @@ meshlet_processor = [
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
|
||||
@ -535,12 +535,12 @@ pub fn extract_clusters(
|
||||
continue;
|
||||
}
|
||||
|
||||
let num_entities: usize = clusters
|
||||
let entity_count: usize = clusters
|
||||
.clusterable_objects
|
||||
.iter()
|
||||
.map(|l| l.entities.len())
|
||||
.sum();
|
||||
let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + num_entities);
|
||||
let mut data = Vec::with_capacity(clusters.clusterable_objects.len() + entity_count);
|
||||
for cluster_objects in &clusters.clusterable_objects {
|
||||
data.push(ExtractedClusterableObjectElement::ClusterHeader(
|
||||
cluster_objects.counts,
|
||||
|
||||
@ -48,6 +48,7 @@ impl Default for AmbientLight {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AmbientLight {
|
||||
pub const NONE: AmbientLight = AmbientLight {
|
||||
color: Color::WHITE,
|
||||
|
||||
@ -33,6 +33,7 @@ pub enum ParallaxMappingMethod {
|
||||
max_steps: u32,
|
||||
},
|
||||
}
|
||||
|
||||
impl ParallaxMappingMethod {
|
||||
/// [`ParallaxMappingMethod::Relief`] with a 5 steps, a reasonable default.
|
||||
pub const DEFAULT_RELIEF_MAPPING: Self = ParallaxMappingMethod::Relief { max_steps: 5 };
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_picking"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides screen picking functionality for Bevy Engine"
|
||||
homepage = "https://bevy.org"
|
||||
@ -13,20 +13,20 @@ bevy_mesh_picking_backend = ["dep:bevy_mesh", "dep:crossbeam-channel"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev", optional = true }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev", optional = true }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user