Merge branch 'bevyengine:main' into proper-json-schema
This commit is contained in:
commit
648bd3d796
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -293,7 +293,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.32.0
|
||||
uses: crate-ci/typos@v1.33.1
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
|
||||
26
Cargo.toml
26
Cargo.toml
@ -134,6 +134,7 @@ default = [
|
||||
"bevy_audio",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_core_widgets",
|
||||
"bevy_anti_aliasing",
|
||||
"bevy_gilrs",
|
||||
"bevy_gizmos",
|
||||
@ -292,6 +293,9 @@ bevy_log = ["bevy_internal/bevy_log"]
|
||||
# Enable input focus subsystem
|
||||
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
|
||||
|
||||
# Headless widget collection for Bevy UI.
|
||||
bevy_core_widgets = ["bevy_internal/bevy_core_widgets"]
|
||||
|
||||
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
|
||||
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
|
||||
|
||||
@ -4438,3 +4442,25 @@ name = "Hotpatching Systems"
|
||||
description = "Demonstrates how to hotpatch systems"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "core_widgets"
|
||||
path = "examples/ui/core_widgets.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.core_widgets]
|
||||
name = "Core Widgets"
|
||||
description = "Demonstrates use of core (headless) widgets in Bevy UI"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "core_widgets_observers"
|
||||
path = "examples/ui/core_widgets_observers.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.core_widgets_observers]
|
||||
name = "Core Widgets (w/Observers)"
|
||||
description = "Demonstrates use of core (headless) widgets in Bevy UI, with Observers"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
@ -1483,8 +1483,8 @@ mod tests {
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::{Event, EventWriter, Events},
|
||||
lifecycle::RemovedComponents,
|
||||
query::With,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
||||
system::{Commands, Query},
|
||||
|
||||
@ -6,9 +6,9 @@ use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
hierarchy::ChildOf,
|
||||
lifecycle::RemovedComponents,
|
||||
query::{Changed, Or, QueryFilter, With, Without},
|
||||
relationship::{Relationship, RelationshipTarget},
|
||||
removal_detection::RemovedComponents,
|
||||
schedule::{IntoScheduleConfigs, SystemSet},
|
||||
system::{Commands, Local, Query},
|
||||
};
|
||||
|
||||
@ -19,12 +19,18 @@ bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
|
||||
# other
|
||||
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
|
||||
rodio = { version = "0.20", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
cpal = { version = "0.15", optional = true }
|
||||
|
||||
[target.'cfg(target_vendor = "apple")'.dependencies]
|
||||
# NOTE: Explicitly depend on this patch version to fix:
|
||||
# https://github.com/bevyengine/bevy/issues/18893
|
||||
coreaudio-sys = { version = "0.2.17", default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
rodio = { version = "0.20", default-features = false, features = [
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! Order Independent Transparency (OIT) for 3d rendering. See [`OrderIndependentTransparencyPlugin`] for more details.
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::{component::*, prelude::*};
|
||||
use bevy_ecs::{component::*, lifecycle::ComponentHook, prelude::*};
|
||||
use bevy_math::UVec2;
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_platform::time::Instant;
|
||||
|
||||
32
crates/bevy_core_widgets/Cargo.toml
Normal file
32
crates/bevy_core_widgets/Cargo.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "bevy_core_widgets"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Unstyled common widgets for B Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_a11y = { path = "../bevy_a11y", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_input = { path = "../bevy_input", version = "0.16.0-dev" }
|
||||
bevy_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev" }
|
||||
bevy_picking = { path = "../bevy_picking", version = "0.16.0-dev" }
|
||||
bevy_ui = { path = "../bevy_ui", version = "0.16.0-dev" }
|
||||
|
||||
# other
|
||||
accesskit = "0.19"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
||||
all-features = true
|
||||
141
crates/bevy_core_widgets/src/core_button.rs
Normal file
141
crates/bevy_core_widgets/src/core_button.rs
Normal file
@ -0,0 +1,141 @@
|
||||
use accesskit::Role;
|
||||
use bevy_a11y::AccessibilityNode;
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_ecs::query::Has;
|
||||
use bevy_ecs::system::ResMut;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
observer::Trigger,
|
||||
query::With,
|
||||
system::{Commands, Query, SystemId},
|
||||
};
|
||||
use bevy_input::keyboard::{KeyCode, KeyboardInput};
|
||||
use bevy_input_focus::{FocusedInput, InputFocus, InputFocusVisible};
|
||||
use bevy_picking::events::{Cancel, Click, DragEnd, Pointer, Press, Release};
|
||||
use bevy_ui::{InteractionDisabled, Pressed};
|
||||
|
||||
/// Headless button widget. This widget maintains a "pressed" state, which is used to
|
||||
/// indicate whether the button is currently being pressed by the user. It emits a `ButtonClicked`
|
||||
/// event when the button is un-pressed.
|
||||
#[derive(Component, Debug)]
|
||||
#[require(AccessibilityNode(accesskit::Node::new(Role::Button)))]
|
||||
pub struct CoreButton {
|
||||
/// Optional system to run when the button is clicked, or when the Enter or Space key
|
||||
/// is pressed while the button is focused. If this field is `None`, the button will
|
||||
/// emit a `ButtonClicked` event when clicked.
|
||||
pub on_click: Option<SystemId>,
|
||||
}
|
||||
|
||||
fn button_on_key_event(
|
||||
mut trigger: Trigger<FocusedInput<KeyboardInput>>,
|
||||
q_state: Query<(&CoreButton, Has<InteractionDisabled>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((bstate, disabled)) = q_state.get(trigger.target().unwrap()) {
|
||||
if !disabled {
|
||||
let event = &trigger.event().input;
|
||||
if !event.repeat
|
||||
&& (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space)
|
||||
{
|
||||
if let Some(on_click) = bstate.on_click {
|
||||
trigger.propagate(false);
|
||||
commands.run_system(on_click);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button_on_pointer_click(
|
||||
mut trigger: Trigger<Pointer<Click>>,
|
||||
mut q_state: Query<(&CoreButton, Has<Pressed>, Has<InteractionDisabled>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((bstate, pressed, disabled)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
trigger.propagate(false);
|
||||
if pressed && !disabled {
|
||||
if let Some(on_click) = bstate.on_click {
|
||||
commands.run_system(on_click);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button_on_pointer_down(
|
||||
mut trigger: Trigger<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().unwrap()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled {
|
||||
if !pressed {
|
||||
commands.entity(button).insert(Pressed);
|
||||
}
|
||||
// Clicking on a button makes it the focused input,
|
||||
// and hides the focus ring if it was visible.
|
||||
if let Some(mut focus) = focus {
|
||||
focus.0 = trigger.target();
|
||||
}
|
||||
if let Some(mut focus_visible) = focus_visible {
|
||||
focus_visible.0 = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button_on_pointer_up(
|
||||
mut trigger: Trigger<Pointer<Release>>,
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled && pressed {
|
||||
commands.entity(button).remove::<Pressed>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button_on_pointer_drag_end(
|
||||
mut trigger: Trigger<Pointer<DragEnd>>,
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled && pressed {
|
||||
commands.entity(button).remove::<Pressed>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn button_on_pointer_cancel(
|
||||
mut trigger: Trigger<Pointer<Cancel>>,
|
||||
mut q_state: Query<(Entity, Has<InteractionDisabled>, Has<Pressed>), With<CoreButton>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok((button, disabled, pressed)) = q_state.get_mut(trigger.target().unwrap()) {
|
||||
trigger.propagate(false);
|
||||
if !disabled && pressed {
|
||||
commands.entity(button).remove::<Pressed>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin that adds the observers for the [`CoreButton`] widget.
|
||||
pub struct CoreButtonPlugin;
|
||||
|
||||
impl Plugin for CoreButtonPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_observer(button_on_key_event)
|
||||
.add_observer(button_on_pointer_down)
|
||||
.add_observer(button_on_pointer_up)
|
||||
.add_observer(button_on_pointer_click)
|
||||
.add_observer(button_on_pointer_drag_end)
|
||||
.add_observer(button_on_pointer_cancel);
|
||||
}
|
||||
}
|
||||
27
crates/bevy_core_widgets/src/lib.rs
Normal file
27
crates/bevy_core_widgets/src/lib.rs
Normal file
@ -0,0 +1,27 @@
|
||||
//! This crate provides a set of core widgets for Bevy UI, such as buttons, checkboxes, and sliders.
|
||||
//! These widgets have no inherent styling, it's the responsibility of the user to add styling
|
||||
//! appropriate for their game or application.
|
||||
//!
|
||||
//! # State Management
|
||||
//!
|
||||
//! Most of the widgets use external state management: this means that the widgets do not
|
||||
//! automatically update their own internal state, but instead rely on the app to update the widget
|
||||
//! state (as well as any other related game state) in response to a change event emitted by the
|
||||
//! widget. The primary motivation for this is to avoid two-way data binding in scenarios where the
|
||||
//! user interface is showing a live view of dynamic data coming from deeper within the game engine.
|
||||
|
||||
mod core_button;
|
||||
|
||||
use bevy_app::{App, Plugin};
|
||||
|
||||
pub use core_button::{CoreButton, CoreButtonPlugin};
|
||||
|
||||
/// A plugin that registers the observers for all of the core widgets. If you don't want to
|
||||
/// use all of the widgets, you can import the individual widget plugins instead.
|
||||
pub struct CoreWidgetsPlugin;
|
||||
|
||||
impl Plugin for CoreWidgetsPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(CoreButtonPlugin);
|
||||
}
|
||||
}
|
||||
@ -94,8 +94,8 @@ impl Plugin for DebugPickingPlugin {
|
||||
log_event_debug::<pointer::PointerInput>.run_if(DebugPickingMode::is_noisy),
|
||||
log_pointer_event_debug::<Over>,
|
||||
log_pointer_event_debug::<Out>,
|
||||
log_pointer_event_debug::<Pressed>,
|
||||
log_pointer_event_debug::<Released>,
|
||||
log_pointer_event_debug::<Press>,
|
||||
log_pointer_event_debug::<Release>,
|
||||
log_pointer_event_debug::<Click>,
|
||||
log_pointer_event_trace::<Move>.run_if(DebugPickingMode::is_noisy),
|
||||
log_pointer_event_debug::<DragStart>,
|
||||
|
||||
@ -60,4 +60,4 @@ mod case4 {
|
||||
pub struct BarTargetOf(Entity);
|
||||
}
|
||||
|
||||
fn foo_hook(_world: bevy_ecs::world::DeferredWorld, _ctx: bevy_ecs::component::HookContext) {}
|
||||
fn foo_hook(_world: bevy_ecs::world::DeferredWorld, _ctx: bevy_ecs::lifecycle::HookContext) {}
|
||||
|
||||
@ -434,7 +434,7 @@ impl HookAttributeKind {
|
||||
HookAttributeKind::Path(path) => path.to_token_stream(),
|
||||
HookAttributeKind::Call(call) => {
|
||||
quote!({
|
||||
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::component::HookContext) {
|
||||
fn _internal_hook(world: #bevy_ecs_path::world::DeferredWorld, ctx: #bevy_ecs_path::lifecycle::HookContext) {
|
||||
(#call)(world, ctx)
|
||||
}
|
||||
_internal_hook
|
||||
@ -658,7 +658,7 @@ fn hook_register_function_call(
|
||||
) -> Option<TokenStream2> {
|
||||
function.map(|meta| {
|
||||
quote! {
|
||||
fn #hook() -> ::core::option::Option<#bevy_ecs_path::component::ComponentHook> {
|
||||
fn #hook() -> ::core::option::Option<#bevy_ecs_path::lifecycle::ComponentHook> {
|
||||
::core::option::Option::Some(#meta)
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,20 @@ enum BundleFieldKind {
|
||||
|
||||
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
|
||||
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
|
||||
const BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS: &str = "ignore_from_components";
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BundleAttributes {
|
||||
impl_from_components: bool,
|
||||
}
|
||||
|
||||
impl Default for BundleAttributes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
impl_from_components: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement the `Bundle` trait.
|
||||
#[proc_macro_derive(Bundle, attributes(bundle))]
|
||||
@ -36,6 +50,27 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
let ecs_path = bevy_ecs_path();
|
||||
|
||||
let mut errors = vec![];
|
||||
|
||||
let mut attributes = BundleAttributes::default();
|
||||
|
||||
for attr in &ast.attrs {
|
||||
if attr.path().is_ident(BUNDLE_ATTRIBUTE_NAME) {
|
||||
let parsing = attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident(BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS) {
|
||||
attributes.impl_from_components = false;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(meta.error(format!("Invalid bundle container attribute. Allowed attributes: `{BUNDLE_ATTRIBUTE_NO_FROM_COMPONENTS}`")))
|
||||
});
|
||||
|
||||
if let Err(error) = parsing {
|
||||
errors.push(error.into_compile_error());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let named_fields = match get_struct_fields(&ast.data, "derive(Bundle)") {
|
||||
Ok(fields) => fields,
|
||||
Err(e) => return e.into_compile_error().into(),
|
||||
@ -130,7 +165,28 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
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)*
|
||||
|
||||
// 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
|
||||
@ -159,20 +215,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
}
|
||||
|
||||
// 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)*
|
||||
}
|
||||
}
|
||||
}
|
||||
#from_components
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
|
||||
|
||||
@ -693,7 +693,7 @@ impl Archetype {
|
||||
|
||||
/// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer
|
||||
///
|
||||
/// [`OnAdd`]: crate::world::OnAdd
|
||||
/// [`OnAdd`]: crate::lifecycle::OnAdd
|
||||
#[inline]
|
||||
pub fn has_add_observer(&self) -> bool {
|
||||
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
|
||||
@ -701,7 +701,7 @@ impl Archetype {
|
||||
|
||||
/// Returns true if any of the components in this archetype have at least one [`OnInsert`] observer
|
||||
///
|
||||
/// [`OnInsert`]: crate::world::OnInsert
|
||||
/// [`OnInsert`]: crate::lifecycle::OnInsert
|
||||
#[inline]
|
||||
pub fn has_insert_observer(&self) -> bool {
|
||||
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
|
||||
@ -709,7 +709,7 @@ impl Archetype {
|
||||
|
||||
/// Returns true if any of the components in this archetype have at least one [`OnReplace`] observer
|
||||
///
|
||||
/// [`OnReplace`]: crate::world::OnReplace
|
||||
/// [`OnReplace`]: crate::lifecycle::OnReplace
|
||||
#[inline]
|
||||
pub fn has_replace_observer(&self) -> bool {
|
||||
self.flags().contains(ArchetypeFlags::ON_REPLACE_OBSERVER)
|
||||
@ -717,7 +717,7 @@ impl Archetype {
|
||||
|
||||
/// Returns true if any of the components in this archetype have at least one [`OnRemove`] observer
|
||||
///
|
||||
/// [`OnRemove`]: crate::world::OnRemove
|
||||
/// [`OnRemove`]: crate::lifecycle::OnRemove
|
||||
#[inline]
|
||||
pub fn has_remove_observer(&self) -> bool {
|
||||
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
|
||||
@ -725,7 +725,7 @@ impl Archetype {
|
||||
|
||||
/// Returns true if any of the components in this archetype have at least one [`OnDespawn`] observer
|
||||
///
|
||||
/// [`OnDespawn`]: crate::world::OnDespawn
|
||||
/// [`OnDespawn`]: crate::lifecycle::OnDespawn
|
||||
#[inline]
|
||||
pub fn has_despawn_observer(&self) -> bool {
|
||||
self.flags().contains(ArchetypeFlags::ON_DESPAWN_OBSERVER)
|
||||
|
||||
@ -2,6 +2,57 @@
|
||||
//!
|
||||
//! This module contains the [`Bundle`] trait and some other helper types.
|
||||
|
||||
/// Derive the [`Bundle`] trait
|
||||
///
|
||||
/// You can apply this derive macro to structs that are
|
||||
/// composed of [`Component`]s or
|
||||
/// other [`Bundle`]s.
|
||||
///
|
||||
/// ## Attributes
|
||||
///
|
||||
/// Sometimes parts of the Bundle should not be inserted.
|
||||
/// Those can be marked with `#[bundle(ignore)]`, and they will be skipped.
|
||||
/// In that case, the field needs to implement [`Default`] unless you also ignore
|
||||
/// the [`BundleFromComponents`] implementation.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::prelude::{Component, Bundle};
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Hitpoint;
|
||||
/// #
|
||||
/// #[derive(Bundle)]
|
||||
/// struct HitpointMarker {
|
||||
/// hitpoints: Hitpoint,
|
||||
///
|
||||
/// #[bundle(ignore)]
|
||||
/// creator: Option<String>
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Some fields may be bundles that do not implement
|
||||
/// [`BundleFromComponents`]. This happens for bundles that cannot be extracted.
|
||||
/// For example with [`SpawnRelatedBundle`](bevy_ecs::spawn::SpawnRelatedBundle), see below for an
|
||||
/// example usage.
|
||||
/// In those cases you can either ignore it as above,
|
||||
/// or you can opt out the whole Struct by marking it as ignored with
|
||||
/// `#[bundle(ignore_from_components)]`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::prelude::{Component, Bundle, ChildOf, Spawn};
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Hitpoint;
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Marker;
|
||||
/// #
|
||||
/// use bevy_ecs::spawn::SpawnRelatedBundle;
|
||||
///
|
||||
/// #[derive(Bundle)]
|
||||
/// #[bundle(ignore_from_components)]
|
||||
/// struct HitpointMarker {
|
||||
/// hitpoints: Hitpoint,
|
||||
/// related_spawner: SpawnRelatedBundle<ChildOf, Spawn<Marker>>,
|
||||
/// }
|
||||
/// ```
|
||||
pub use bevy_ecs_macros::Bundle;
|
||||
|
||||
use crate::{
|
||||
@ -15,15 +66,13 @@ use crate::{
|
||||
RequiredComponents, StorageType, Tick,
|
||||
},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
lifecycle::{ON_ADD, ON_INSERT, ON_REMOVE, ON_REPLACE},
|
||||
observer::Observers,
|
||||
prelude::World,
|
||||
query::DebugCheckedUnwrap,
|
||||
relationship::RelationshipHookMode,
|
||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||
world::{
|
||||
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
|
||||
ON_REPLACE,
|
||||
},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut},
|
||||
};
|
||||
use alloc::{boxed::Box, vec, vec::Vec};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
@ -2072,7 +2121,7 @@ fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, remove: &[T]) {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
archetype::ArchetypeCreated, component::HookContext, prelude::*, world::DeferredWorld,
|
||||
archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld,
|
||||
};
|
||||
use alloc::vec;
|
||||
|
||||
@ -2122,6 +2171,26 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
#[bundle(ignore_from_components)]
|
||||
struct BundleNoExtract {
|
||||
b: B,
|
||||
no_from_comp: crate::spawn::SpawnRelatedBundle<ChildOf, Spawn<C>>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_spawn_bundle_without_extract() {
|
||||
let mut world = World::new();
|
||||
let id = world
|
||||
.spawn(BundleNoExtract {
|
||||
b: B,
|
||||
no_from_comp: Children::spawn(Spawn(C)),
|
||||
})
|
||||
.id();
|
||||
|
||||
assert!(world.entity(id).get::<Children>().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn component_hook_order_spawn_despawn() {
|
||||
let mut world = World::new();
|
||||
|
||||
@ -5,16 +5,17 @@ use crate::{
|
||||
bundle::BundleInfo,
|
||||
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
||||
entity::{ComponentCloneCtx, Entity, EntityMapper, SourceComponent},
|
||||
lifecycle::{ComponentHook, ComponentHooks},
|
||||
query::DebugCheckedUnwrap,
|
||||
relationship::RelationshipHookMode,
|
||||
resource::Resource,
|
||||
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
||||
system::{Local, SystemParam},
|
||||
world::{DeferredWorld, FromWorld, World},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::{borrow::Cow, format, vec::Vec};
|
||||
pub use bevy_ecs_macros::Component;
|
||||
use bevy_ecs_macros::Event;
|
||||
use bevy_platform::sync::Arc;
|
||||
use bevy_platform::{
|
||||
collections::{HashMap, HashSet},
|
||||
@ -375,7 +376,8 @@ use thiserror::Error;
|
||||
/// - `#[component(on_remove = on_remove_function)]`
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::{Component, HookContext};
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::lifecycle::HookContext;
|
||||
/// # use bevy_ecs::world::DeferredWorld;
|
||||
/// # use bevy_ecs::entity::Entity;
|
||||
/// # use bevy_ecs::component::ComponentId;
|
||||
@ -404,7 +406,8 @@ use thiserror::Error;
|
||||
/// This also supports function calls that yield closures
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::{Component, HookContext};
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::lifecycle::HookContext;
|
||||
/// # use bevy_ecs::world::DeferredWorld;
|
||||
/// #
|
||||
/// #[derive(Component)]
|
||||
@ -656,244 +659,6 @@ pub enum StorageType {
|
||||
SparseSet,
|
||||
}
|
||||
|
||||
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
|
||||
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext);
|
||||
|
||||
/// Context provided to a [`ComponentHook`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HookContext {
|
||||
/// The [`Entity`] this hook was invoked for.
|
||||
pub entity: Entity,
|
||||
/// The [`ComponentId`] this hook was invoked for.
|
||||
pub component_id: ComponentId,
|
||||
/// The caller location is `Some` if the `track_caller` feature is enabled.
|
||||
pub caller: MaybeLocation,
|
||||
/// Configures how relationship hooks will run
|
||||
pub relationship_hook_mode: RelationshipHookMode,
|
||||
}
|
||||
|
||||
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
|
||||
///
|
||||
/// Hooks are functions that run when a component is added, overwritten, or removed from an entity.
|
||||
/// These are intended to be used for structural side effects that need to happen when a component is added or removed,
|
||||
/// and are not intended for general-purpose logic.
|
||||
///
|
||||
/// For example, you might use a hook to update a cached index when a component is added,
|
||||
/// to clean up resources when a component is removed,
|
||||
/// or to keep hierarchical data structures across entities in sync.
|
||||
///
|
||||
/// This information is stored in the [`ComponentInfo`] of the associated component.
|
||||
///
|
||||
/// There is two ways of configuring hooks for a component:
|
||||
/// 1. Defining the relevant hooks on the [`Component`] implementation
|
||||
/// 2. Using the [`World::register_component_hooks`] method
|
||||
///
|
||||
/// # Example 2
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_platform::collections::HashSet;
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// struct MyTrackedComponent;
|
||||
///
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct TrackedEntities(HashSet<Entity>);
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// world.init_resource::<TrackedEntities>();
|
||||
///
|
||||
/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks
|
||||
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
|
||||
/// assert!(tracked_component_query.iter(&world).next().is_none());
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.insert(context.entity);
|
||||
/// });
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.remove(&context.entity);
|
||||
/// });
|
||||
///
|
||||
/// let entity = world.spawn(MyTrackedComponent).id();
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(tracked_entities.0.contains(&entity));
|
||||
///
|
||||
/// world.despawn(entity);
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(!tracked_entities.0.contains(&entity));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ComponentHooks {
|
||||
pub(crate) on_add: Option<ComponentHook>,
|
||||
pub(crate) on_insert: Option<ComponentHook>,
|
||||
pub(crate) on_replace: Option<ComponentHook>,
|
||||
pub(crate) on_remove: Option<ComponentHook>,
|
||||
pub(crate) on_despawn: Option<ComponentHook>,
|
||||
}
|
||||
|
||||
impl ComponentHooks {
|
||||
pub(crate) fn update_from_component<C: Component + ?Sized>(&mut self) -> &mut Self {
|
||||
if let Some(hook) = C::on_add() {
|
||||
self.on_add(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_insert() {
|
||||
self.on_insert(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_replace() {
|
||||
self.on_replace(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_remove() {
|
||||
self.on_remove(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_despawn() {
|
||||
self.on_despawn(hook);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is added to an entity.
|
||||
/// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as
|
||||
/// adding all of its components.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_add` hook
|
||||
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_add(hook)
|
||||
.expect("Component already has an on_add hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
|
||||
/// or replaced.
|
||||
///
|
||||
/// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component).
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
|
||||
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_insert` hook
|
||||
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_insert(hook)
|
||||
.expect("Component already has an on_insert hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
|
||||
/// such as being replaced (with `.insert`) or removed.
|
||||
///
|
||||
/// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced,
|
||||
/// allowing access to the previous data just before it is dropped.
|
||||
/// This hook does *not* run if the entity did not already have this component.
|
||||
///
|
||||
/// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity).
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
|
||||
/// As a result, this is *not* an appropriate mechanism for reliably updating indexes and other caches.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_replace` hook
|
||||
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_replace(hook)
|
||||
.expect("Component already has an on_replace hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
|
||||
/// Despawning an entity counts as removing all of its components.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_remove` hook
|
||||
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_remove(hook)
|
||||
.expect("Component already has an on_remove hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_despawn` hook
|
||||
pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_despawn(hook)
|
||||
.expect("Component already has an on_despawn hook")
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_add`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_add` hook.
|
||||
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_add.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_add = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_insert`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_insert` hook.
|
||||
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_insert.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_insert = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_replace`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_replace` hook.
|
||||
pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_replace.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_replace = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity.
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_remove`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_remove` hook.
|
||||
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_remove.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_remove = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_despawn`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_despawn` hook.
|
||||
pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_despawn.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_despawn = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores metadata for a type of component or resource stored in a specific [`World`].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ComponentInfo {
|
||||
@ -2052,7 +1817,7 @@ impl Components {
|
||||
}
|
||||
|
||||
/// Gets the metadata associated with the given component, if it is registered.
|
||||
/// This will return `None` if the id is not regiserted or is queued.
|
||||
/// This will return `None` if the id is not registered or is queued.
|
||||
///
|
||||
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
|
||||
#[inline]
|
||||
@ -2616,7 +2381,7 @@ impl Tick {
|
||||
///
|
||||
/// Returns `true` if wrapping was performed. Otherwise, returns `false`.
|
||||
#[inline]
|
||||
pub(crate) fn check_tick(&mut self, tick: Tick) -> bool {
|
||||
pub fn check_tick(&mut self, tick: Tick) -> bool {
|
||||
let age = tick.relative_to(*self);
|
||||
// This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true
|
||||
// so long as this check always runs before that can happen.
|
||||
@ -2629,6 +2394,41 @@ impl Tick {
|
||||
}
|
||||
}
|
||||
|
||||
/// An observer [`Event`] that can be used to maintain [`Tick`]s in custom data structures, enabling to make
|
||||
/// use of bevy's periodic checks that clamps ticks to a certain range, preventing overflows and thus
|
||||
/// keeping methods like [`Tick::is_newer_than`] reliably return `false` for ticks that got too old.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Here a schedule is stored in a custom resource. This way the systems in it would not have their change
|
||||
/// ticks automatically updated via [`World::check_change_ticks`], possibly causing `Tick`-related bugs on
|
||||
/// long-running apps.
|
||||
///
|
||||
/// To fix that, add an observer for this event that calls the schedule's
|
||||
/// [`Schedule::check_change_ticks`](bevy_ecs::schedule::Schedule::check_change_ticks).
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_ecs::component::CheckChangeTicks;
|
||||
///
|
||||
/// #[derive(Resource)]
|
||||
/// struct CustomSchedule(Schedule);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// world.add_observer(|tick: Trigger<CheckChangeTicks>, mut schedule: ResMut<CustomSchedule>| {
|
||||
/// schedule.0.check_change_ticks(tick.get());
|
||||
/// });
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, Event)]
|
||||
pub struct CheckChangeTicks(pub(crate) Tick);
|
||||
|
||||
impl CheckChangeTicks {
|
||||
/// Get the `Tick` that can be used as the parameter of [`Tick::check_tick`].
|
||||
pub fn get(self) -> Tick {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Interior-mutable access to the [`Tick`]s for a single component or resource.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct TickCells<'a> {
|
||||
|
||||
@ -68,7 +68,7 @@ pub trait Event: Send + Sync + 'static {
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This method should not be overridden by implementors,
|
||||
/// This method should not be overridden by implementers,
|
||||
/// and should always correspond to the implementation of [`component_id`](Event::component_id).
|
||||
fn register_component_id(world: &mut World) -> ComponentId {
|
||||
world.register_component::<EventWrapperComponent<Self>>()
|
||||
@ -82,7 +82,7 @@ pub trait Event: Send + Sync + 'static {
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// This method should not be overridden by implementors,
|
||||
/// This method should not be overridden by implementers,
|
||||
/// and should always correspond to the implementation of [`register_component_id`](Event::register_component_id).
|
||||
fn component_id(world: &World) -> Option<ComponentId> {
|
||||
world.component_id::<EventWrapperComponent<Self>>()
|
||||
|
||||
@ -10,8 +10,9 @@
|
||||
use crate::reflect::{ReflectComponent, ReflectFromWorld};
|
||||
use crate::{
|
||||
bundle::Bundle,
|
||||
component::{Component, HookContext},
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
lifecycle::HookContext,
|
||||
relationship::{RelatedSpawner, RelatedSpawnerCommands},
|
||||
system::EntityCommands,
|
||||
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
|
||||
|
||||
@ -41,6 +41,7 @@ pub mod event;
|
||||
pub mod hierarchy;
|
||||
pub mod intern;
|
||||
pub mod label;
|
||||
pub mod lifecycle;
|
||||
pub mod name;
|
||||
pub mod never;
|
||||
pub mod observer;
|
||||
@ -48,7 +49,6 @@ pub mod query;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub mod reflect;
|
||||
pub mod relationship;
|
||||
pub mod removal_detection;
|
||||
pub mod resource;
|
||||
pub mod schedule;
|
||||
pub mod spawn;
|
||||
@ -76,12 +76,12 @@ pub mod prelude {
|
||||
error::{BevyError, Result},
|
||||
event::{Event, EventMutator, EventReader, EventWriter, Events},
|
||||
hierarchy::{ChildOf, ChildSpawner, ChildSpawnerCommands, Children},
|
||||
lifecycle::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace, RemovedComponents},
|
||||
name::{Name, NameOrEntity},
|
||||
observer::{Observer, Trigger},
|
||||
query::{Added, Allows, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
|
||||
related,
|
||||
relationship::RelationshipTarget,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::{
|
||||
common_conditions::*, ApplyDeferred, IntoScheduleConfigs, IntoSystemSet, Schedule,
|
||||
@ -96,7 +96,7 @@ pub mod prelude {
|
||||
},
|
||||
world::{
|
||||
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
|
||||
FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World,
|
||||
FromWorld, World,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
606
crates/bevy_ecs/src/lifecycle.rs
Normal file
606
crates/bevy_ecs/src/lifecycle.rs
Normal file
@ -0,0 +1,606 @@
|
||||
//! This module contains various tools to allow you to react to component insertion or removal,
|
||||
//! as well as entity spawning and despawning.
|
||||
//!
|
||||
//! There are four main ways to react to these lifecycle events:
|
||||
//!
|
||||
//! 1. Using component hooks, which act as inherent constructors and destructors for components.
|
||||
//! 2. Using [observers], which are a user-extensible way to respond to events, including component lifecycle events.
|
||||
//! 3. Using the [`RemovedComponents`] system parameter, which offers an event-style interface.
|
||||
//! 4. Using the [`Added`] query filter, which checks each component to see if it has been added since the last time a system ran.
|
||||
//!
|
||||
//! [observers]: crate::observer
|
||||
//! [`Added`]: crate::query::Added
|
||||
//!
|
||||
//! # Types of lifecycle events
|
||||
//!
|
||||
//! There are five types of lifecycle events, split into two categories. First, we have lifecycle events that are triggered
|
||||
//! when a component is added to an entity:
|
||||
//!
|
||||
//! - [`OnAdd`]: Triggered when a component is added to an entity that did not already have it.
|
||||
//! - [`OnInsert`]: Triggered when a component is added to an entity, regardless of whether it already had it.
|
||||
//!
|
||||
//! When both events occur, [`OnAdd`] hooks are evaluated before [`OnInsert`].
|
||||
//!
|
||||
//! Next, we have lifecycle events that are triggered when a component is removed from an entity:
|
||||
//!
|
||||
//! - [`OnReplace`]: Triggered when a component is removed from an entity, regardless if it is then replaced with a new value.
|
||||
//! - [`OnRemove`]: Triggered when a component is removed from an entity and not replaced, before the component is removed.
|
||||
//! - [`OnDespawn`]: Triggered for each component on an entity when it is despawned.
|
||||
//!
|
||||
//! [`OnReplace`] hooks are evaluated before [`OnRemove`], then finally [`OnDespawn`] hooks are evaluated.
|
||||
//!
|
||||
//! [`OnAdd`] and [`OnRemove`] are counterparts: they are only triggered when a component is added or removed
|
||||
//! from an entity in such a way as to cause a change in the component's presence on that entity.
|
||||
//! Similarly, [`OnInsert`] and [`OnReplace`] are counterparts: they are triggered when a component is added or replaced
|
||||
//! on an entity, regardless of whether this results in a change in the component's presence on that entity.
|
||||
//!
|
||||
//! To reliably synchronize data structures using with component lifecycle events,
|
||||
//! you can combine [`OnInsert`] and [`OnReplace`] to fully capture any changes to the data.
|
||||
//! This is particularly useful in combination with immutable components,
|
||||
//! to avoid any lifecycle-bypassing mutations.
|
||||
//!
|
||||
//! ## Lifecycle events and component types
|
||||
//!
|
||||
//! Despite the absence of generics, each lifecycle event is associated with a specific component.
|
||||
//! When defining a component hook for a [`Component`] type, that component is used.
|
||||
//! When listening to lifecycle events for observers, the `B: Bundle` generic is used.
|
||||
//!
|
||||
//! Each of these lifecycle events also corresponds to a fixed [`ComponentId`],
|
||||
//! which are assigned during [`World`] initialization.
|
||||
//! For example, [`OnAdd`] corresponds to [`ON_ADD`].
|
||||
//! This is used to skip [`TypeId`](core::any::TypeId) lookups in hot paths.
|
||||
use crate::{
|
||||
change_detection::MaybeLocation,
|
||||
component::{Component, ComponentId, ComponentIdFor, Tick},
|
||||
entity::Entity,
|
||||
event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events},
|
||||
relationship::RelationshipHookMode,
|
||||
storage::SparseSet,
|
||||
system::{Local, ReadOnlySystemParam, SystemMeta, SystemParam},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
};
|
||||
|
||||
use derive_more::derive::Into;
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
option,
|
||||
};
|
||||
|
||||
/// The type used for [`Component`] lifecycle hooks such as `on_add`, `on_insert` or `on_remove`.
|
||||
pub type ComponentHook = for<'w> fn(DeferredWorld<'w>, HookContext);
|
||||
|
||||
/// Context provided to a [`ComponentHook`].
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct HookContext {
|
||||
/// The [`Entity`] this hook was invoked for.
|
||||
pub entity: Entity,
|
||||
/// The [`ComponentId`] this hook was invoked for.
|
||||
pub component_id: ComponentId,
|
||||
/// The caller location is `Some` if the `track_caller` feature is enabled.
|
||||
pub caller: MaybeLocation,
|
||||
/// Configures how relationship hooks will run
|
||||
pub relationship_hook_mode: RelationshipHookMode,
|
||||
}
|
||||
|
||||
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
|
||||
///
|
||||
/// Hooks are functions that run when a component is added, overwritten, or removed from an entity.
|
||||
/// These are intended to be used for structural side effects that need to happen when a component is added or removed,
|
||||
/// and are not intended for general-purpose logic.
|
||||
///
|
||||
/// For example, you might use a hook to update a cached index when a component is added,
|
||||
/// to clean up resources when a component is removed,
|
||||
/// or to keep hierarchical data structures across entities in sync.
|
||||
///
|
||||
/// This information is stored in the [`ComponentInfo`](crate::component::ComponentInfo) of the associated component.
|
||||
///
|
||||
/// There are two ways of configuring hooks for a component:
|
||||
/// 1. Defining the relevant hooks on the [`Component`] implementation
|
||||
/// 2. Using the [`World::register_component_hooks`] method
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use bevy_ecs::prelude::*;
|
||||
/// use bevy_platform::collections::HashSet;
|
||||
///
|
||||
/// #[derive(Component)]
|
||||
/// struct MyTrackedComponent;
|
||||
///
|
||||
/// #[derive(Resource, Default)]
|
||||
/// struct TrackedEntities(HashSet<Entity>);
|
||||
///
|
||||
/// let mut world = World::new();
|
||||
/// world.init_resource::<TrackedEntities>();
|
||||
///
|
||||
/// // No entities with `MyTrackedComponent` have been added yet, so we can safely add component hooks
|
||||
/// let mut tracked_component_query = world.query::<&MyTrackedComponent>();
|
||||
/// assert!(tracked_component_query.iter(&world).next().is_none());
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_add(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.insert(context.entity);
|
||||
/// });
|
||||
///
|
||||
/// world.register_component_hooks::<MyTrackedComponent>().on_remove(|mut world, context| {
|
||||
/// let mut tracked_entities = world.resource_mut::<TrackedEntities>();
|
||||
/// tracked_entities.0.remove(&context.entity);
|
||||
/// });
|
||||
///
|
||||
/// let entity = world.spawn(MyTrackedComponent).id();
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(tracked_entities.0.contains(&entity));
|
||||
///
|
||||
/// world.despawn(entity);
|
||||
/// let tracked_entities = world.resource::<TrackedEntities>();
|
||||
/// assert!(!tracked_entities.0.contains(&entity));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ComponentHooks {
|
||||
pub(crate) on_add: Option<ComponentHook>,
|
||||
pub(crate) on_insert: Option<ComponentHook>,
|
||||
pub(crate) on_replace: Option<ComponentHook>,
|
||||
pub(crate) on_remove: Option<ComponentHook>,
|
||||
pub(crate) on_despawn: Option<ComponentHook>,
|
||||
}
|
||||
|
||||
impl ComponentHooks {
|
||||
pub(crate) fn update_from_component<C: Component + ?Sized>(&mut self) -> &mut Self {
|
||||
if let Some(hook) = C::on_add() {
|
||||
self.on_add(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_insert() {
|
||||
self.on_insert(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_replace() {
|
||||
self.on_replace(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_remove() {
|
||||
self.on_remove(hook);
|
||||
}
|
||||
if let Some(hook) = C::on_despawn() {
|
||||
self.on_despawn(hook);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is added to an entity.
|
||||
/// An `on_add` hook will always run before `on_insert` hooks. Spawning an entity counts as
|
||||
/// adding all of its components.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_add` hook
|
||||
pub fn on_add(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_add(hook)
|
||||
.expect("Component already has an on_add hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
|
||||
/// or replaced.
|
||||
///
|
||||
/// An `on_insert` hook always runs after any `on_add` hooks (if the entity didn't already have the component).
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
|
||||
/// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_insert` hook
|
||||
pub fn on_insert(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_insert(hook)
|
||||
.expect("Component already has an on_insert hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is about to be dropped,
|
||||
/// such as being replaced (with `.insert`) or removed.
|
||||
///
|
||||
/// If this component is inserted onto an entity that already has it, this hook will run before the value is replaced,
|
||||
/// allowing access to the previous data just before it is dropped.
|
||||
/// This hook does *not* run if the entity did not already have this component.
|
||||
///
|
||||
/// An `on_replace` hook always runs before any `on_remove` hooks (if the component is being removed from the entity).
|
||||
///
|
||||
/// # Warning
|
||||
///
|
||||
/// The hook won't run if the component is already present and is only mutated, such as in a system via a query.
|
||||
/// As a result, this needs to be combined with immutable components to serve as a mechanism for reliably updating indexes and other caches.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_replace` hook
|
||||
pub fn on_replace(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_replace(hook)
|
||||
.expect("Component already has an on_replace hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run when this component is removed from an entity.
|
||||
/// Despawning an entity counts as removing all of its components.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_remove` hook
|
||||
pub fn on_remove(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_remove(hook)
|
||||
.expect("Component already has an on_remove hook")
|
||||
}
|
||||
|
||||
/// Register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the component already has an `on_despawn` hook
|
||||
pub fn on_despawn(&mut self, hook: ComponentHook) -> &mut Self {
|
||||
self.try_on_despawn(hook)
|
||||
.expect("Component already has an on_despawn hook")
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is added to an entity.
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_add`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_add` hook.
|
||||
pub fn try_on_add(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_add.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_add = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is added (with `.insert`)
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_insert`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_insert` hook.
|
||||
pub fn try_on_insert(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_insert.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_insert = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is replaced (with `.insert`) or removed
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_replace`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_replace` hook.
|
||||
pub fn try_on_replace(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_replace.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_replace = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run when this component is removed from an entity.
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_remove`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_remove` hook.
|
||||
pub fn try_on_remove(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_remove.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_remove = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
|
||||
/// Attempt to register a [`ComponentHook`] that will be run for each component on an entity when it is despawned.
|
||||
///
|
||||
/// This is a fallible version of [`Self::on_despawn`].
|
||||
///
|
||||
/// Returns `None` if the component already has an `on_despawn` hook.
|
||||
pub fn try_on_despawn(&mut self, hook: ComponentHook) -> Option<&mut Self> {
|
||||
if self.on_despawn.is_some() {
|
||||
return None;
|
||||
}
|
||||
self.on_despawn = Some(hook);
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// [`ComponentId`] for [`OnAdd`]
|
||||
pub const ON_ADD: ComponentId = ComponentId::new(0);
|
||||
/// [`ComponentId`] for [`OnInsert`]
|
||||
pub const ON_INSERT: ComponentId = ComponentId::new(1);
|
||||
/// [`ComponentId`] for [`OnReplace`]
|
||||
pub const ON_REPLACE: ComponentId = ComponentId::new(2);
|
||||
/// [`ComponentId`] for [`OnRemove`]
|
||||
pub const ON_REMOVE: ComponentId = ComponentId::new(3);
|
||||
/// [`ComponentId`] for [`OnDespawn`]
|
||||
pub const ON_DESPAWN: ComponentId = ComponentId::new(4);
|
||||
|
||||
/// Trigger emitted when a component is inserted onto an entity that does not already have that
|
||||
/// component. Runs before `OnInsert`.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_add`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnAdd;
|
||||
|
||||
/// Trigger emitted when a component is inserted, regardless of whether or not the entity already
|
||||
/// had that component. Runs after `OnAdd`, if it ran.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_insert`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnInsert;
|
||||
|
||||
/// Trigger emitted when a component is removed from an entity, regardless
|
||||
/// of whether or not it is later replaced.
|
||||
///
|
||||
/// Runs before the value is replaced, so you can still access the original component data.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_replace`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnReplace;
|
||||
|
||||
/// Trigger emitted when a component is removed from an entity, and runs before the component is
|
||||
/// removed, so you can still access the component data.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_remove`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnRemove;
|
||||
|
||||
/// Trigger emitted for each component on an entity when it is despawned.
|
||||
/// See [`crate::lifecycle::ComponentHooks::on_despawn`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnDespawn;
|
||||
|
||||
/// Wrapper around [`Entity`] for [`RemovedComponents`].
|
||||
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
|
||||
#[derive(Event, Debug, Clone, Into)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))]
|
||||
pub struct RemovedComponentEntity(Entity);
|
||||
|
||||
/// Wrapper around a [`EventCursor<RemovedComponentEntity>`] so that we
|
||||
/// can differentiate events between components.
|
||||
#[derive(Debug)]
|
||||
pub struct RemovedComponentReader<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
reader: EventCursor<RemovedComponentEntity>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> Default for RemovedComponentReader<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reader: Default::default(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Deref for RemovedComponentReader<T> {
|
||||
type Target = EventCursor<RemovedComponentEntity>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> DerefMut for RemovedComponentReader<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.reader
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct RemovedComponentEvents {
|
||||
event_sets: SparseSet<ComponentId, Events<RemovedComponentEntity>>,
|
||||
}
|
||||
|
||||
impl RemovedComponentEvents {
|
||||
/// Creates an empty storage buffer for component removal events.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// For each type of component, swaps the event buffers and clears the oldest event buffer.
|
||||
/// In general, this should be called once per frame/update.
|
||||
pub fn update(&mut self) {
|
||||
for (_component_id, events) in self.event_sets.iter_mut() {
|
||||
events.update();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over components and their entity events.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&ComponentId, &Events<RemovedComponentEntity>)> {
|
||||
self.event_sets.iter()
|
||||
}
|
||||
|
||||
/// Gets the event storage for a given component.
|
||||
pub fn get(
|
||||
&self,
|
||||
component_id: impl Into<ComponentId>,
|
||||
) -> Option<&Events<RemovedComponentEntity>> {
|
||||
self.event_sets.get(component_id.into())
|
||||
}
|
||||
|
||||
/// Sends a removal event for the specified component.
|
||||
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
|
||||
self.event_sets
|
||||
.get_or_insert_with(component_id.into(), Default::default)
|
||||
.send(RemovedComponentEntity(entity));
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SystemParam`] that yields entities that had their `T` [`Component`]
|
||||
/// removed or have been despawned with it.
|
||||
///
|
||||
/// This acts effectively the same as an [`EventReader`](crate::event::EventReader).
|
||||
///
|
||||
/// Unlike hooks or observers (see the [lifecycle](crate) module docs),
|
||||
/// this does not allow you to see which data existed before removal.
|
||||
///
|
||||
/// If you are using `bevy_ecs` as a standalone crate,
|
||||
/// note that the [`RemovedComponents`] list will not be automatically cleared for you,
|
||||
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers).
|
||||
///
|
||||
/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is
|
||||
/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`.
|
||||
/// For the main world, this is delayed until after all `SubApp`s have run.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::system::IntoSystem;
|
||||
/// # use bevy_ecs::lifecycle::RemovedComponents;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct MyComponent;
|
||||
/// fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
|
||||
/// removed.read().for_each(|removed_entity| println!("{}", removed_entity));
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(react_on_removal);
|
||||
/// ```
|
||||
#[derive(SystemParam)]
|
||||
pub struct RemovedComponents<'w, 's, T: Component> {
|
||||
component_id: ComponentIdFor<'s, T>,
|
||||
reader: Local<'s, RemovedComponentReader<T>>,
|
||||
event_sets: &'w RemovedComponentEvents,
|
||||
}
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIter<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<iter::Cloned<EventIterator<'a, RemovedComponentEntity>>>>,
|
||||
fn(RemovedComponentEntity) -> Entity,
|
||||
>;
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIterWithId<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<EventIteratorWithId<'a, RemovedComponentEntity>>>,
|
||||
fn(
|
||||
(&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>),
|
||||
>;
|
||||
|
||||
fn map_id_events(
|
||||
(entity, id): (&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>) {
|
||||
(entity.clone().into(), id)
|
||||
}
|
||||
|
||||
// For all practical purposes, the api surface of `RemovedComponents<T>`
|
||||
// should be similar to `EventReader<T>` to reduce confusion.
|
||||
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
|
||||
/// Fetch underlying [`EventCursor`].
|
||||
pub fn reader(&self) -> &EventCursor<RemovedComponentEntity> {
|
||||
&self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`EventCursor`] mutably.
|
||||
pub fn reader_mut(&mut self) -> &mut EventCursor<RemovedComponentEntity> {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`Events`].
|
||||
pub fn events(&self) -> Option<&Events<RemovedComponentEntity>> {
|
||||
self.event_sets.get(self.component_id.get())
|
||||
}
|
||||
|
||||
/// Destructures to get a mutable reference to the `EventCursor`
|
||||
/// and a reference to `Events`.
|
||||
///
|
||||
/// This is necessary since Rust can't detect destructuring through methods and most
|
||||
/// usecases of the reader uses the `Events` as well.
|
||||
pub fn reader_mut_with_events(
|
||||
&mut self,
|
||||
) -> Option<(
|
||||
&mut RemovedComponentReader<T>,
|
||||
&Events<RemovedComponentEntity>,
|
||||
)> {
|
||||
self.event_sets
|
||||
.get(self.component_id.get())
|
||||
.map(|events| (&mut *self.reader, events))
|
||||
}
|
||||
|
||||
/// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the
|
||||
/// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events
|
||||
/// that happened before now.
|
||||
pub fn read(&mut self) -> RemovedIter<'_> {
|
||||
self.reader_mut_with_events()
|
||||
.map(|(reader, events)| reader.read(events).cloned())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(RemovedComponentEntity::into)
|
||||
}
|
||||
|
||||
/// Like [`read`](Self::read), except also returning the [`EventId`] of the events.
|
||||
pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> {
|
||||
self.reader_mut_with_events()
|
||||
.map(|(reader, events)| reader.read_with_id(events))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(map_id_events)
|
||||
}
|
||||
|
||||
/// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any.
|
||||
pub fn len(&self) -> usize {
|
||||
self.events()
|
||||
.map(|events| self.reader.len(events))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no events available to read.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events()
|
||||
.is_none_or(|events| self.reader.is_empty(events))
|
||||
}
|
||||
|
||||
/// Consumes all available events.
|
||||
///
|
||||
/// This means these events will not appear in calls to [`RemovedComponents::read()`] or
|
||||
/// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`.
|
||||
pub fn clear(&mut self) {
|
||||
if let Some((reader, events)) = self.reader_mut_with_events() {
|
||||
reader.clear(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Only reads World removed component events
|
||||
unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}
|
||||
|
||||
// SAFETY: no component value access.
|
||||
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
|
||||
type State = ();
|
||||
type Item<'w, 's> = &'w RemovedComponentEvents;
|
||||
|
||||
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.removed_components()
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,7 @@
|
||||
use crate::{
|
||||
component::{
|
||||
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
|
||||
},
|
||||
component::{Component, ComponentCloneBehavior, Mutable, StorageType},
|
||||
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, EntityMapper, SourceComponent},
|
||||
lifecycle::{ComponentHook, HookContext},
|
||||
world::World,
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
@ -391,6 +391,8 @@ pub struct Observers {
|
||||
|
||||
impl Observers {
|
||||
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ON_ADD => &mut self.on_add,
|
||||
ON_INSERT => &mut self.on_insert,
|
||||
@ -402,6 +404,8 @@ impl Observers {
|
||||
}
|
||||
|
||||
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ON_ADD => Some(&self.on_add),
|
||||
ON_INSERT => Some(&self.on_insert),
|
||||
@ -479,6 +483,8 @@ impl Observers {
|
||||
}
|
||||
|
||||
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
|
||||
use crate::lifecycle::*;
|
||||
|
||||
match event_type {
|
||||
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
|
||||
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
|
||||
|
||||
@ -2,8 +2,9 @@ use alloc::{boxed::Box, vec};
|
||||
use core::any::Any;
|
||||
|
||||
use crate::{
|
||||
component::{ComponentHook, ComponentId, HookContext, Mutable, StorageType},
|
||||
component::{ComponentId, Mutable, StorageType},
|
||||
error::{ErrorContext, ErrorHandler},
|
||||
lifecycle::{ComponentHook, HookContext},
|
||||
observer::{ObserverDescriptor, ObserverTrigger},
|
||||
prelude::*,
|
||||
query::DebugCheckedUnwrap,
|
||||
@ -410,7 +411,7 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`ComponentHook`] used by [`Observer`] to handle its [`on-add`](`crate::component::ComponentHooks::on_add`).
|
||||
/// 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.
|
||||
|
||||
@ -11,9 +11,10 @@ pub use relationship_query::*;
|
||||
pub use relationship_source_collection::*;
|
||||
|
||||
use crate::{
|
||||
component::{Component, HookContext, Mutable},
|
||||
component::{Component, Mutable},
|
||||
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
||||
error::{ignore, CommandWithEntity, HandleError},
|
||||
lifecycle::HookContext,
|
||||
world::{DeferredWorld, EntityWorldMut},
|
||||
};
|
||||
use log::warn;
|
||||
|
||||
@ -1,268 +0,0 @@
|
||||
//! Alerting events when a component is removed from an entity.
|
||||
|
||||
use crate::{
|
||||
component::{Component, ComponentId, ComponentIdFor, Tick},
|
||||
entity::Entity,
|
||||
event::{Event, EventCursor, EventId, EventIterator, EventIteratorWithId, Events},
|
||||
prelude::Local,
|
||||
storage::SparseSet,
|
||||
system::{ReadOnlySystemParam, SystemMeta, SystemParam},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||
};
|
||||
|
||||
use derive_more::derive::Into;
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
use core::{
|
||||
fmt::Debug,
|
||||
iter,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
option,
|
||||
};
|
||||
|
||||
/// Wrapper around [`Entity`] for [`RemovedComponents`].
|
||||
/// Internally, `RemovedComponents` uses these as an `Events<RemovedComponentEntity>`.
|
||||
#[derive(Event, Debug, Clone, Into)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug, Clone))]
|
||||
pub struct RemovedComponentEntity(Entity);
|
||||
|
||||
/// Wrapper around a [`EventCursor<RemovedComponentEntity>`] so that we
|
||||
/// can differentiate events between components.
|
||||
#[derive(Debug)]
|
||||
pub struct RemovedComponentReader<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
reader: EventCursor<RemovedComponentEntity>,
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Component> Default for RemovedComponentReader<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
reader: Default::default(),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> Deref for RemovedComponentReader<T> {
|
||||
type Target = EventCursor<RemovedComponentEntity>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.reader
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> DerefMut for RemovedComponentReader<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.reader
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the [`RemovedComponents`] event buffers for all types of component in a given [`World`].
|
||||
#[derive(Default, Debug)]
|
||||
pub struct RemovedComponentEvents {
|
||||
event_sets: SparseSet<ComponentId, Events<RemovedComponentEntity>>,
|
||||
}
|
||||
|
||||
impl RemovedComponentEvents {
|
||||
/// Creates an empty storage buffer for component removal events.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// For each type of component, swaps the event buffers and clears the oldest event buffer.
|
||||
/// In general, this should be called once per frame/update.
|
||||
pub fn update(&mut self) {
|
||||
for (_component_id, events) in self.event_sets.iter_mut() {
|
||||
events.update();
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over components and their entity events.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&ComponentId, &Events<RemovedComponentEntity>)> {
|
||||
self.event_sets.iter()
|
||||
}
|
||||
|
||||
/// Gets the event storage for a given component.
|
||||
pub fn get(
|
||||
&self,
|
||||
component_id: impl Into<ComponentId>,
|
||||
) -> Option<&Events<RemovedComponentEntity>> {
|
||||
self.event_sets.get(component_id.into())
|
||||
}
|
||||
|
||||
/// Sends a removal event for the specified component.
|
||||
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
|
||||
self.event_sets
|
||||
.get_or_insert_with(component_id.into(), Default::default)
|
||||
.send(RemovedComponentEntity(entity));
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`SystemParam`] that yields entities that had their `T` [`Component`]
|
||||
/// removed or have been despawned with it.
|
||||
///
|
||||
/// This acts effectively the same as an [`EventReader`](crate::event::EventReader).
|
||||
///
|
||||
/// Note that this does not allow you to see which data existed before removal.
|
||||
/// If you need this, you will need to track the component data value on your own,
|
||||
/// using a regularly scheduled system that requests `Query<(Entity, &T), Changed<T>>`
|
||||
/// and stores the data somewhere safe to later cross-reference.
|
||||
///
|
||||
/// If you are using `bevy_ecs` as a standalone crate,
|
||||
/// note that the `RemovedComponents` list will not be automatically cleared for you,
|
||||
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers).
|
||||
///
|
||||
/// For users of `bevy` and `bevy_app`, [`World::clear_trackers`](World::clear_trackers) is
|
||||
/// automatically called by `bevy_app::App::update` and `bevy_app::SubApp::update`.
|
||||
/// For the main world, this is delayed until after all `SubApp`s have run.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::system::IntoSystem;
|
||||
/// # use bevy_ecs::removal_detection::RemovedComponents;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct MyComponent;
|
||||
/// fn react_on_removal(mut removed: RemovedComponents<MyComponent>) {
|
||||
/// removed.read().for_each(|removed_entity| println!("{}", removed_entity));
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(react_on_removal);
|
||||
/// ```
|
||||
#[derive(SystemParam)]
|
||||
pub struct RemovedComponents<'w, 's, T: Component> {
|
||||
component_id: ComponentIdFor<'s, T>,
|
||||
reader: Local<'s, RemovedComponentReader<T>>,
|
||||
event_sets: &'w RemovedComponentEvents,
|
||||
}
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIter<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<iter::Cloned<EventIterator<'a, RemovedComponentEntity>>>>,
|
||||
fn(RemovedComponentEntity) -> Entity,
|
||||
>;
|
||||
|
||||
/// Iterator over entities that had a specific component removed.
|
||||
///
|
||||
/// See [`RemovedComponents`].
|
||||
pub type RemovedIterWithId<'a> = iter::Map<
|
||||
iter::Flatten<option::IntoIter<EventIteratorWithId<'a, RemovedComponentEntity>>>,
|
||||
fn(
|
||||
(&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>),
|
||||
>;
|
||||
|
||||
fn map_id_events(
|
||||
(entity, id): (&RemovedComponentEntity, EventId<RemovedComponentEntity>),
|
||||
) -> (Entity, EventId<RemovedComponentEntity>) {
|
||||
(entity.clone().into(), id)
|
||||
}
|
||||
|
||||
// For all practical purposes, the api surface of `RemovedComponents<T>`
|
||||
// should be similar to `EventReader<T>` to reduce confusion.
|
||||
impl<'w, 's, T: Component> RemovedComponents<'w, 's, T> {
|
||||
/// Fetch underlying [`EventCursor`].
|
||||
pub fn reader(&self) -> &EventCursor<RemovedComponentEntity> {
|
||||
&self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`EventCursor`] mutably.
|
||||
pub fn reader_mut(&mut self) -> &mut EventCursor<RemovedComponentEntity> {
|
||||
&mut self.reader
|
||||
}
|
||||
|
||||
/// Fetch underlying [`Events`].
|
||||
pub fn events(&self) -> Option<&Events<RemovedComponentEntity>> {
|
||||
self.event_sets.get(self.component_id.get())
|
||||
}
|
||||
|
||||
/// Destructures to get a mutable reference to the `EventCursor`
|
||||
/// and a reference to `Events`.
|
||||
///
|
||||
/// This is necessary since Rust can't detect destructuring through methods and most
|
||||
/// usecases of the reader uses the `Events` as well.
|
||||
pub fn reader_mut_with_events(
|
||||
&mut self,
|
||||
) -> Option<(
|
||||
&mut RemovedComponentReader<T>,
|
||||
&Events<RemovedComponentEntity>,
|
||||
)> {
|
||||
self.event_sets
|
||||
.get(self.component_id.get())
|
||||
.map(|events| (&mut *self.reader, events))
|
||||
}
|
||||
|
||||
/// Iterates over the events this [`RemovedComponents`] has not seen yet. This updates the
|
||||
/// [`RemovedComponents`]'s event counter, which means subsequent event reads will not include events
|
||||
/// that happened before now.
|
||||
pub fn read(&mut self) -> RemovedIter<'_> {
|
||||
self.reader_mut_with_events()
|
||||
.map(|(reader, events)| reader.read(events).cloned())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(RemovedComponentEntity::into)
|
||||
}
|
||||
|
||||
/// Like [`read`](Self::read), except also returning the [`EventId`] of the events.
|
||||
pub fn read_with_id(&mut self) -> RemovedIterWithId<'_> {
|
||||
self.reader_mut_with_events()
|
||||
.map(|(reader, events)| reader.read_with_id(events))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.map(map_id_events)
|
||||
}
|
||||
|
||||
/// Determines the number of removal events available to be read from this [`RemovedComponents`] without consuming any.
|
||||
pub fn len(&self) -> usize {
|
||||
self.events()
|
||||
.map(|events| self.reader.len(events))
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Returns `true` if there are no events available to read.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.events()
|
||||
.is_none_or(|events| self.reader.is_empty(events))
|
||||
}
|
||||
|
||||
/// Consumes all available events.
|
||||
///
|
||||
/// This means these events will not appear in calls to [`RemovedComponents::read()`] or
|
||||
/// [`RemovedComponents::read_with_id()`] and [`RemovedComponents::is_empty()`] will return `true`.
|
||||
pub fn clear(&mut self) {
|
||||
if let Some((reader, events)) = self.reader_mut_with_events() {
|
||||
reader.clear(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Only reads World removed component events
|
||||
unsafe impl<'a> ReadOnlySystemParam for &'a RemovedComponentEvents {}
|
||||
|
||||
// SAFETY: no component value access.
|
||||
unsafe impl<'a> SystemParam for &'a RemovedComponentEvents {
|
||||
type State = ();
|
||||
type Item<'w, 's> = &'w RemovedComponentEvents;
|
||||
|
||||
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State {}
|
||||
|
||||
#[inline]
|
||||
unsafe fn get_param<'w, 's>(
|
||||
_state: &'s mut Self::State,
|
||||
_system_meta: &SystemMeta,
|
||||
world: UnsafeWorldCell<'w>,
|
||||
_change_tick: Tick,
|
||||
) -> Self::Item<'w, 's> {
|
||||
world.removed_components()
|
||||
}
|
||||
}
|
||||
@ -11,8 +11,18 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
|
||||
|
||||
/// A system that determines if one or more scheduled systems should run.
|
||||
///
|
||||
/// Implemented for functions and closures that convert into [`System<Out=bool>`](System)
|
||||
/// with [read-only](crate::system::ReadOnlySystemParam) parameters.
|
||||
/// `SystemCondition` is sealed and implemented for functions and closures with
|
||||
/// [read-only](crate::system::ReadOnlySystemParam) parameters that convert into
|
||||
/// [`System<Out = bool>`](System), [`System<Out = Result<(), BevyError>>`](System) or
|
||||
/// [`System<Out = Result<bool, BevyError>>`](System).
|
||||
///
|
||||
/// `SystemCondition` offers a private method
|
||||
/// (called by [`run_if`](crate::schedule::IntoScheduleConfigs::run_if) and the provided methods)
|
||||
/// that converts the implementing system into a condition (system) returning a bool.
|
||||
/// Depending on the output type of the implementing system:
|
||||
/// - `bool`: the implementing system is used as the condition;
|
||||
/// - `Result<(), BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(())`;
|
||||
/// - `Result<bool, BevyError>`: the condition returns `true` if and only if the implementing system returns `Ok(true)`.
|
||||
///
|
||||
/// # Marker type parameter
|
||||
///
|
||||
@ -31,7 +41,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
|
||||
/// ```
|
||||
///
|
||||
/// # Examples
|
||||
/// A condition that returns true every other time it's called.
|
||||
/// A condition that returns `true` every other time it's called.
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// fn every_other_time() -> impl SystemCondition<()> {
|
||||
@ -54,7 +64,7 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
|
||||
/// # assert!(!world.resource::<DidRun>().0);
|
||||
/// ```
|
||||
///
|
||||
/// A condition that takes a bool as an input and returns it unchanged.
|
||||
/// A condition that takes a `bool` as an input and returns it unchanged.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
@ -71,8 +81,30 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
|
||||
/// # world.insert_resource(DidRun(false));
|
||||
/// # app.run(&mut world);
|
||||
/// # assert!(world.resource::<DidRun>().0);
|
||||
pub trait SystemCondition<Marker, In: SystemInput = ()>:
|
||||
sealed::SystemCondition<Marker, In>
|
||||
/// ```
|
||||
///
|
||||
/// A condition returning a `Result<(), BevyError>`
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # #[derive(Component)] struct Player;
|
||||
/// fn player_exists(q_player: Query<(), With<Player>>) -> Result {
|
||||
/// Ok(q_player.single()?)
|
||||
/// }
|
||||
///
|
||||
/// # let mut app = Schedule::default();
|
||||
/// # #[derive(Resource)] struct DidRun(bool);
|
||||
/// # fn my_system(mut did_run: ResMut<DidRun>) { did_run.0 = true; }
|
||||
/// app.add_systems(my_system.run_if(player_exists));
|
||||
/// # let mut world = World::new();
|
||||
/// # world.insert_resource(DidRun(false));
|
||||
/// # app.run(&mut world);
|
||||
/// # assert!(!world.resource::<DidRun>().0);
|
||||
/// # world.spawn(Player);
|
||||
/// # app.run(&mut world);
|
||||
/// # assert!(world.resource::<DidRun>().0);
|
||||
pub trait SystemCondition<Marker, In: SystemInput = (), Out = bool>:
|
||||
sealed::SystemCondition<Marker, In, Out>
|
||||
{
|
||||
/// Returns a new run condition that only returns `true`
|
||||
/// if both this one and the passed `and` return `true`.
|
||||
@ -371,28 +403,61 @@ pub trait SystemCondition<Marker, In: SystemInput = ()>:
|
||||
}
|
||||
}
|
||||
|
||||
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F where
|
||||
F: sealed::SystemCondition<Marker, In>
|
||||
impl<Marker, In: SystemInput, Out, F> SystemCondition<Marker, In, Out> for F where
|
||||
F: sealed::SystemCondition<Marker, In, Out>
|
||||
{
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
use crate::system::{IntoSystem, ReadOnlySystem, SystemInput};
|
||||
use crate::{
|
||||
error::BevyError,
|
||||
system::{IntoSystem, ReadOnlySystem, SystemInput},
|
||||
};
|
||||
|
||||
pub trait SystemCondition<Marker, In: SystemInput>:
|
||||
IntoSystem<In, bool, Marker, System = Self::ReadOnlySystem>
|
||||
pub trait SystemCondition<Marker, In: SystemInput, Out>:
|
||||
IntoSystem<In, Out, Marker, System = Self::ReadOnlySystem>
|
||||
{
|
||||
// This associated type is necessary to let the compiler
|
||||
// know that `Self::System` is `ReadOnlySystem`.
|
||||
type ReadOnlySystem: ReadOnlySystem<In = In, Out = bool>;
|
||||
type ReadOnlySystem: ReadOnlySystem<In = In, Out = Out>;
|
||||
|
||||
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool>;
|
||||
}
|
||||
|
||||
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In> for F
|
||||
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, bool> for F
|
||||
where
|
||||
F: IntoSystem<In, bool, Marker>,
|
||||
F::System: ReadOnlySystem,
|
||||
{
|
||||
type ReadOnlySystem = F::System;
|
||||
|
||||
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
|
||||
IntoSystem::into_system(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<(), BevyError>> for F
|
||||
where
|
||||
F: IntoSystem<In, Result<(), BevyError>, Marker>,
|
||||
F::System: ReadOnlySystem,
|
||||
{
|
||||
type ReadOnlySystem = F::System;
|
||||
|
||||
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
|
||||
IntoSystem::into_system(self.map(|result| result.is_ok()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<Marker, In: SystemInput, F> SystemCondition<Marker, In, Result<bool, BevyError>> for F
|
||||
where
|
||||
F: IntoSystem<In, Result<bool, BevyError>, Marker>,
|
||||
F::System: ReadOnlySystem,
|
||||
{
|
||||
type ReadOnlySystem = F::System;
|
||||
|
||||
fn into_condition_system(self) -> impl ReadOnlySystem<In = In, Out = bool> {
|
||||
IntoSystem::into_system(self.map(|result| matches!(result, Ok(true))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,9 +467,9 @@ pub mod common_conditions {
|
||||
use crate::{
|
||||
change_detection::DetectChanges,
|
||||
event::{Event, EventReader},
|
||||
lifecycle::RemovedComponents,
|
||||
prelude::{Component, Query, With},
|
||||
query::QueryFilter,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
system::{In, IntoSystem, Local, Res, System, SystemInput},
|
||||
};
|
||||
|
||||
@ -14,8 +14,8 @@ use crate::{
|
||||
system::{BoxedSystem, InfallibleSystemWrapper, IntoSystem, ScheduleSystem, System},
|
||||
};
|
||||
|
||||
fn new_condition<M>(condition: impl SystemCondition<M>) -> BoxedCondition {
|
||||
let condition_system = IntoSystem::into_system(condition);
|
||||
fn new_condition<M, Out>(condition: impl SystemCondition<M, (), Out>) -> BoxedCondition {
|
||||
let condition_system = condition.into_condition_system();
|
||||
assert!(
|
||||
condition_system.is_send(),
|
||||
"SystemCondition `{}` accesses `NonSend` resources. This is not currently supported.",
|
||||
@ -447,7 +447,7 @@ pub trait IntoScheduleConfigs<T: Schedulable<Metadata = GraphInfo, GroupMetadata
|
||||
///
|
||||
/// Use [`distributive_run_if`](IntoScheduleConfigs::distributive_run_if) if you want the
|
||||
/// condition to be evaluated for each individual system, right before one is run.
|
||||
fn run_if<M>(self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
|
||||
fn run_if<M, Out>(self, condition: impl SystemCondition<M, (), Out>) -> ScheduleConfigs<T> {
|
||||
self.into_configs().run_if(condition)
|
||||
}
|
||||
|
||||
@ -535,7 +535,7 @@ impl<T: Schedulable<Metadata = GraphInfo, GroupMetadata = Chain>> IntoScheduleCo
|
||||
self
|
||||
}
|
||||
|
||||
fn run_if<M>(mut self, condition: impl SystemCondition<M>) -> ScheduleConfigs<T> {
|
||||
fn run_if<M, Out>(mut self, condition: impl SystemCondition<M, (), Out>) -> ScheduleConfigs<T> {
|
||||
self.run_if_dyn(new_condition(condition));
|
||||
self
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ use crate::{
|
||||
component::{ComponentId, Tick},
|
||||
error::{BevyError, ErrorContext, Result},
|
||||
prelude::{IntoSystemSet, SystemSet},
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::{BoxedCondition, InternedSystemSet, NodeId, SystemTypeSet},
|
||||
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
@ -162,12 +162,8 @@ impl System for ApplyDeferred {
|
||||
Cow::Borrowed("bevy_ecs::apply_deferred")
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
// This system accesses no components.
|
||||
const { &Access::new() }
|
||||
}
|
||||
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
// This system accesses no components.
|
||||
const { &FilteredAccessSet::new() }
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ mod tests {
|
||||
use alloc::{string::ToString, vec, vec::Vec};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use crate::error::BevyError;
|
||||
pub use crate::{
|
||||
prelude::World,
|
||||
resource::Resource,
|
||||
@ -49,10 +50,10 @@ mod tests {
|
||||
struct SystemOrder(Vec<u32>);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct RunConditionBool(pub bool);
|
||||
struct RunConditionBool(bool);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
struct Counter(pub AtomicU32);
|
||||
struct Counter(AtomicU32);
|
||||
|
||||
fn make_exclusive_system(tag: u32) -> impl FnMut(&mut World) {
|
||||
move |world| world.resource_mut::<SystemOrder>().0.push(tag)
|
||||
@ -252,12 +253,13 @@ mod tests {
|
||||
}
|
||||
|
||||
mod conditions {
|
||||
|
||||
use crate::change_detection::DetectChanges;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn system_with_condition() {
|
||||
fn system_with_condition_bool() {
|
||||
let mut world = World::default();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
@ -276,6 +278,47 @@ mod tests {
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_with_condition_result_unit() {
|
||||
let mut world = World::default();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_systems(
|
||||
make_function_system(0).run_if(|| Err::<(), BevyError>(core::fmt::Error.into())),
|
||||
);
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
|
||||
|
||||
schedule.add_systems(make_function_system(1).run_if(|| Ok(())));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn system_with_condition_result_bool() {
|
||||
let mut world = World::default();
|
||||
let mut schedule = Schedule::default();
|
||||
|
||||
world.init_resource::<SystemOrder>();
|
||||
|
||||
schedule.add_systems((
|
||||
make_function_system(0).run_if(|| Err::<bool, BevyError>(core::fmt::Error.into())),
|
||||
make_function_system(1).run_if(|| Ok(false)),
|
||||
));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![]);
|
||||
|
||||
schedule.add_systems(make_function_system(2).run_if(|| Ok(true)));
|
||||
|
||||
schedule.run(&mut world);
|
||||
assert_eq!(world.resource::<SystemOrder>().0, vec![2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn systems_with_distributive_condition() {
|
||||
let mut world = World::default();
|
||||
|
||||
@ -166,7 +166,7 @@ impl Schedules {
|
||||
writeln!(message, "{}", components.get_name(*id).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
info!("{}", message);
|
||||
info!("{message}");
|
||||
}
|
||||
|
||||
/// Adds one or more systems to the [`Schedule`] matching the provided [`ScheduleLabel`].
|
||||
@ -558,7 +558,7 @@ impl Schedule {
|
||||
/// Iterates the change ticks of all systems in the schedule and clamps any older than
|
||||
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
|
||||
/// This prevents overflow and thus prevents false positives.
|
||||
pub(crate) fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
pub fn check_change_ticks(&mut self, change_tick: Tick) {
|
||||
for system in &mut self.executable.systems {
|
||||
if !is_apply_deferred(system) {
|
||||
system.check_change_tick(change_tick);
|
||||
@ -1705,10 +1705,7 @@ impl ScheduleGraph {
|
||||
match self.settings.hierarchy_detection {
|
||||
LogLevel::Ignore => unreachable!(),
|
||||
LogLevel::Warn => {
|
||||
error!(
|
||||
"Schedule {schedule_label:?} has redundant edges:\n {}",
|
||||
message
|
||||
);
|
||||
error!("Schedule {schedule_label:?} has redundant edges:\n {message}");
|
||||
Ok(())
|
||||
}
|
||||
LogLevel::Error => Err(ScheduleBuildError::HierarchyRedundancy(message)),
|
||||
@ -1910,7 +1907,7 @@ impl ScheduleGraph {
|
||||
match self.settings.ambiguity_detection {
|
||||
LogLevel::Ignore => Ok(()),
|
||||
LogLevel::Warn => {
|
||||
warn!("Schedule {schedule_label:?} has ambiguities.\n{}", message);
|
||||
warn!("Schedule {schedule_label:?} has ambiguities.\n{message}");
|
||||
Ok(())
|
||||
}
|
||||
LogLevel::Error => Err(ScheduleBuildError::Ambiguity(message)),
|
||||
|
||||
@ -60,7 +60,93 @@ define_label!(
|
||||
);
|
||||
|
||||
define_label!(
|
||||
/// Types that identify logical groups of systems.
|
||||
/// System sets are tag-like labels that can be used to group systems together.
|
||||
///
|
||||
/// This allows you to share configuration (like run conditions) across multiple systems,
|
||||
/// and order systems or system sets relative to conceptual groups of systems.
|
||||
/// To control the behavior of a system set as a whole, use [`Schedule::configure_sets`](crate::prelude::Schedule::configure_sets),
|
||||
/// or the method of the same name on `App`.
|
||||
///
|
||||
/// Systems can belong to any number of system sets, reflecting multiple roles or facets that they might have.
|
||||
/// For example, you may want to annotate a system as "consumes input" and "applies forces",
|
||||
/// and ensure that your systems are ordered correctly for both of those sets.
|
||||
///
|
||||
/// System sets can belong to any number of other system sets,
|
||||
/// allowing you to create nested hierarchies of system sets to group systems together.
|
||||
/// Configuration applied to system sets will flow down to their members (including other system sets),
|
||||
/// allowing you to set and modify the configuration in a single place.
|
||||
///
|
||||
/// Systems sets are also useful for exposing a consistent public API for dependencies
|
||||
/// to hook into across versions of your crate,
|
||||
/// allowing them to add systems to a specific set, or order relative to that set,
|
||||
/// without leaking implementation details of the exact systems involved.
|
||||
///
|
||||
/// ## Defining new system sets
|
||||
///
|
||||
/// To create a new system set, use the `#[derive(SystemSet)]` macro.
|
||||
/// Unit structs are a good choice for one-off sets.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// struct PhysicsSystems;
|
||||
/// ```
|
||||
///
|
||||
/// When you want to define several related system sets,
|
||||
/// consider creating an enum system set.
|
||||
/// Each variant will be treated as a separate system set.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// enum CombatSystems {
|
||||
/// TargetSelection,
|
||||
/// DamageCalculation,
|
||||
/// Cleanup,
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// By convention, the listed order of the system set in the enum
|
||||
/// corresponds to the order in which the systems are run.
|
||||
/// Ordering must be explicitly added to ensure that this is the case,
|
||||
/// but following this convention will help avoid confusion.
|
||||
///
|
||||
/// ### Adding systems to system sets
|
||||
///
|
||||
/// To add systems to a system set, call [`in_set`](crate::prelude::IntoScheduleConfigs::in_set) on the system function
|
||||
/// while adding it to your app or schedule.
|
||||
///
|
||||
/// Like usual, these methods can be chained with other configuration methods like [`before`](crate::prelude::IntoScheduleConfigs::before),
|
||||
/// or repeated to add systems to multiple sets.
|
||||
///
|
||||
/// ```rust
|
||||
/// use bevy_ecs::prelude::*;
|
||||
///
|
||||
/// #[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
/// enum CombatSystems {
|
||||
/// TargetSelection,
|
||||
/// DamageCalculation,
|
||||
/// Cleanup,
|
||||
/// }
|
||||
///
|
||||
/// fn target_selection() {}
|
||||
///
|
||||
/// fn enemy_damage_calculation() {}
|
||||
///
|
||||
/// fn player_damage_calculation() {}
|
||||
///
|
||||
/// let mut schedule = Schedule::default();
|
||||
/// // Configuring the sets to run in order.
|
||||
/// schedule.configure_sets((CombatSystems::TargetSelection, CombatSystems::DamageCalculation, CombatSystems::Cleanup).chain());
|
||||
///
|
||||
/// // Adding a single system to a set.
|
||||
/// schedule.add_systems(target_selection.in_set(CombatSystems::TargetSelection));
|
||||
///
|
||||
/// // Adding multiple systems to a set.
|
||||
/// schedule.add_systems((player_damage_calculation, enemy_damage_calculation).in_set(CombatSystems::DamageCalculation));
|
||||
/// ```
|
||||
#[diagnostic::on_unimplemented(
|
||||
note = "consider annotating `{Self}` with `#[derive(SystemSet)]`"
|
||||
)]
|
||||
|
||||
@ -475,9 +475,8 @@ impl Stepping {
|
||||
Some(state) => state.clear_behaviors(),
|
||||
None => {
|
||||
warn!(
|
||||
"stepping is not enabled for schedule {:?}; \
|
||||
use `.add_stepping({:?})` to enable stepping",
|
||||
label, label
|
||||
"stepping is not enabled for schedule {label:?}; \
|
||||
use `.add_stepping({label:?})` to enable stepping"
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -486,9 +485,8 @@ impl Stepping {
|
||||
Some(state) => state.set_behavior(system, behavior),
|
||||
None => {
|
||||
warn!(
|
||||
"stepping is not enabled for schedule {:?}; \
|
||||
use `.add_stepping({:?})` to enable stepping",
|
||||
label, label
|
||||
"stepping is not enabled for schedule {label:?}; \
|
||||
use `.add_stepping({label:?})` to enable stepping"
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -498,9 +496,8 @@ impl Stepping {
|
||||
Some(state) => state.clear_behavior(system),
|
||||
None => {
|
||||
warn!(
|
||||
"stepping is not enabled for schedule {:?}; \
|
||||
use `.add_stepping({:?})` to enable stepping",
|
||||
label, label
|
||||
"stepping is not enabled for schedule {label:?}; \
|
||||
use `.add_stepping({label:?})` to enable stepping"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,10 +127,6 @@ where
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &crate::query::Access<crate::component::ComponentId> {
|
||||
self.system.component_access()
|
||||
}
|
||||
|
||||
fn component_access_set(
|
||||
&self,
|
||||
) -> &crate::query::FilteredAccessSet<crate::component::ComponentId> {
|
||||
|
||||
@ -4,7 +4,7 @@ use core::marker::PhantomData;
|
||||
use crate::{
|
||||
component::{ComponentId, Tick},
|
||||
prelude::World,
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::InternedSystemSet,
|
||||
system::{input::SystemInput, SystemIn, SystemParamValidationError},
|
||||
world::unsafe_world_cell::UnsafeWorldCell,
|
||||
@ -144,10 +144,6 @@ where
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.component_access_set.combined_access()
|
||||
}
|
||||
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
&self.component_access_set
|
||||
}
|
||||
@ -363,10 +359,6 @@ where
|
||||
self.name.clone()
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.component_access_set.combined_access()
|
||||
}
|
||||
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
&self.component_access_set
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
component::{ComponentId, Tick},
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::{InternedSystemSet, SystemSet},
|
||||
system::{
|
||||
check_system_change_tick, ExclusiveSystemParam, ExclusiveSystemParamItem, IntoSystem,
|
||||
@ -87,11 +87,6 @@ where
|
||||
self.system_meta.name.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.system_meta.component_access_set.combined_access()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
&self.system_meta.component_access_set
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
component::{ComponentId, Tick},
|
||||
prelude::FromWorld,
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::{InternedSystemSet, SystemSet},
|
||||
system::{
|
||||
check_system_change_tick, ReadOnlySystemParam, System, SystemIn, SystemInput, SystemParam,
|
||||
@ -620,11 +620,6 @@ where
|
||||
self.system_meta.name.clone()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.system_meta.component_access_set.combined_access()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
&self.system_meta.component_access_set
|
||||
|
||||
@ -97,7 +97,7 @@
|
||||
//! - [`EventWriter`](crate::event::EventWriter)
|
||||
//! - [`NonSend`] and `Option<NonSend>`
|
||||
//! - [`NonSendMut`] and `Option<NonSendMut>`
|
||||
//! - [`RemovedComponents`](crate::removal_detection::RemovedComponents)
|
||||
//! - [`RemovedComponents`](crate::lifecycle::RemovedComponents)
|
||||
//! - [`SystemName`]
|
||||
//! - [`SystemChangeTick`]
|
||||
//! - [`Archetypes`](crate::archetype::Archetypes) (Provides Archetype metadata)
|
||||
@ -408,10 +408,10 @@ mod tests {
|
||||
component::{Component, Components},
|
||||
entity::{Entities, Entity},
|
||||
error::Result,
|
||||
lifecycle::RemovedComponents,
|
||||
name::Name,
|
||||
prelude::{AnyOf, EntityRef, Trigger},
|
||||
prelude::{AnyOf, EntityRef, OnAdd, Trigger},
|
||||
query::{Added, Changed, Or, SpawnDetails, Spawned, With, Without},
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::{
|
||||
common_conditions::resource_exists, ApplyDeferred, IntoScheduleConfigs, Schedule,
|
||||
@ -421,7 +421,7 @@ mod tests {
|
||||
Commands, In, InMut, IntoSystem, Local, NonSend, NonSendMut, ParamSet, Query, Res,
|
||||
ResMut, Single, StaticSystemParam, System, SystemState,
|
||||
},
|
||||
world::{DeferredWorld, EntityMut, FromWorld, OnAdd, World},
|
||||
world::{DeferredWorld, EntityMut, FromWorld, World},
|
||||
};
|
||||
|
||||
use super::ScheduleSystem;
|
||||
@ -1166,7 +1166,9 @@ mod tests {
|
||||
x.initialize(&mut world);
|
||||
y.initialize(&mut world);
|
||||
|
||||
let conflicts = x.component_access().get_conflicts(y.component_access());
|
||||
let conflicts = x
|
||||
.component_access_set()
|
||||
.get_conflicts(y.component_access_set());
|
||||
let b_id = world
|
||||
.components()
|
||||
.get_resource_id(TypeId::of::<B>())
|
||||
|
||||
@ -6,7 +6,7 @@ use crate::{
|
||||
error::Result,
|
||||
never::Never,
|
||||
prelude::{Bundle, Trigger},
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::{Fallible, Infallible},
|
||||
system::{input::SystemIn, System},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
@ -116,11 +116,6 @@ where
|
||||
self.observer.name()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.observer.component_access()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
self.observer.component_access_set()
|
||||
|
||||
@ -3,7 +3,7 @@ use alloc::{borrow::Cow, vec::Vec};
|
||||
use crate::{
|
||||
component::{ComponentId, Tick},
|
||||
error::Result,
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
system::{input::SystemIn, BoxedSystem, System, SystemInput},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
|
||||
};
|
||||
@ -33,11 +33,6 @@ impl<S: System<In = ()>> System for InfallibleSystemWrapper<S> {
|
||||
self.0.type_id()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.0.component_access()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
self.0.component_access_set()
|
||||
@ -154,10 +149,6 @@ where
|
||||
self.system.name()
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.system.component_access()
|
||||
}
|
||||
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
self.system.component_access_set()
|
||||
}
|
||||
@ -256,10 +247,6 @@ where
|
||||
self.system.name()
|
||||
}
|
||||
|
||||
fn component_access(&self) -> &Access<ComponentId> {
|
||||
self.system.component_access()
|
||||
}
|
||||
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId> {
|
||||
self.system.component_access_set()
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ use thiserror::Error;
|
||||
|
||||
use crate::{
|
||||
component::{ComponentId, Tick},
|
||||
query::{Access, FilteredAccessSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::InternedSystemSet,
|
||||
system::{input::SystemInput, SystemIn},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
@ -57,9 +57,6 @@ pub trait System: Send + Sync + 'static {
|
||||
TypeId::of::<Self>()
|
||||
}
|
||||
|
||||
/// Returns the system's component [`Access`].
|
||||
fn component_access(&self) -> &Access<ComponentId>;
|
||||
|
||||
/// Returns the system's component [`FilteredAccessSet`].
|
||||
fn component_access_set(&self) -> &FilteredAccessSet<ComponentId>;
|
||||
|
||||
|
||||
@ -1,55 +0,0 @@
|
||||
//! Internal components used by bevy with a fixed component id.
|
||||
//! Constants are used to skip [`TypeId`] lookups in hot paths.
|
||||
use super::*;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::Reflect;
|
||||
|
||||
/// [`ComponentId`] for [`OnAdd`]
|
||||
pub const ON_ADD: ComponentId = ComponentId::new(0);
|
||||
/// [`ComponentId`] for [`OnInsert`]
|
||||
pub const ON_INSERT: ComponentId = ComponentId::new(1);
|
||||
/// [`ComponentId`] for [`OnReplace`]
|
||||
pub const ON_REPLACE: ComponentId = ComponentId::new(2);
|
||||
/// [`ComponentId`] for [`OnRemove`]
|
||||
pub const ON_REMOVE: ComponentId = ComponentId::new(3);
|
||||
/// [`ComponentId`] for [`OnDespawn`]
|
||||
pub const ON_DESPAWN: ComponentId = ComponentId::new(4);
|
||||
|
||||
/// Trigger emitted when a component is inserted onto an entity that does not already have that
|
||||
/// component. Runs before `OnInsert`.
|
||||
/// See [`crate::component::ComponentHooks::on_add`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnAdd;
|
||||
|
||||
/// Trigger emitted when a component is inserted, regardless of whether or not the entity already
|
||||
/// had that component. Runs after `OnAdd`, if it ran.
|
||||
/// See [`crate::component::ComponentHooks::on_insert`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnInsert;
|
||||
|
||||
/// Trigger emitted when a component is inserted onto an entity that already has that component.
|
||||
/// Runs before the value is replaced, so you can still access the original component data.
|
||||
/// See [`crate::component::ComponentHooks::on_replace`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnReplace;
|
||||
|
||||
/// Trigger emitted when a component is removed from an entity, and runs before the component is
|
||||
/// removed, so you can still access the component data.
|
||||
/// See [`crate::component::ComponentHooks::on_remove`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnRemove;
|
||||
|
||||
/// Trigger emitted for each component on an entity when it is despawned.
|
||||
/// See [`crate::component::ComponentHooks::on_despawn`] for more information.
|
||||
#[derive(Event, Debug)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(feature = "bevy_reflect", reflect(Debug))]
|
||||
pub struct OnDespawn;
|
||||
@ -3,9 +3,10 @@ use core::ops::Deref;
|
||||
use crate::{
|
||||
archetype::Archetype,
|
||||
change_detection::{MaybeLocation, MutUntyped},
|
||||
component::{ComponentId, HookContext, Mutable},
|
||||
component::{ComponentId, Mutable},
|
||||
entity::Entity,
|
||||
event::{Event, EventId, Events, SendBatchIds},
|
||||
lifecycle::{HookContext, ON_INSERT, ON_REPLACE},
|
||||
observer::{Observers, TriggerTargets},
|
||||
prelude::{Component, QueryState},
|
||||
query::{QueryData, QueryFilter},
|
||||
@ -16,7 +17,7 @@ use crate::{
|
||||
world::{error::EntityMutableFetchError, EntityFetcher, WorldEntityFetch},
|
||||
};
|
||||
|
||||
use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World, ON_INSERT, ON_REPLACE};
|
||||
use super::{unsafe_world_cell::UnsafeWorldCell, Mut, World};
|
||||
|
||||
/// A [`World`] reference that disallows structural ECS changes.
|
||||
/// This includes initializing resources, registering components or spawning entities.
|
||||
|
||||
@ -14,15 +14,13 @@ use crate::{
|
||||
EntityIdLocation, EntityLocation,
|
||||
},
|
||||
event::Event,
|
||||
lifecycle::{ON_DESPAWN, ON_REMOVE, ON_REPLACE},
|
||||
observer::Observer,
|
||||
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||
relationship::RelationshipHookMode,
|
||||
resource::Resource,
|
||||
system::IntoObserverSystem,
|
||||
world::{
|
||||
error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World,
|
||||
ON_DESPAWN, ON_REMOVE, ON_REPLACE,
|
||||
},
|
||||
world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World},
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
@ -2609,14 +2607,14 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// # Panics
|
||||
///
|
||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||
pub fn entry<'a, T: Component>(&'a mut self) -> Entry<'w, 'a, T> {
|
||||
pub fn entry<'a, T: Component>(&'a mut self) -> ComponentEntry<'w, 'a, T> {
|
||||
if self.contains::<T>() {
|
||||
Entry::Occupied(OccupiedEntry {
|
||||
ComponentEntry::Occupied(OccupiedComponentEntry {
|
||||
entity_world: self,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
} else {
|
||||
Entry::Vacant(VacantEntry {
|
||||
ComponentEntry::Vacant(VacantComponentEntry {
|
||||
entity_world: self,
|
||||
_marker: PhantomData,
|
||||
})
|
||||
@ -2859,14 +2857,14 @@ impl<'w> EntityWorldMut<'w> {
|
||||
/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`].
|
||||
///
|
||||
/// [`entry`]: EntityWorldMut::entry
|
||||
pub enum Entry<'w, 'a, T: Component> {
|
||||
pub enum ComponentEntry<'w, 'a, T: Component> {
|
||||
/// An occupied entry.
|
||||
Occupied(OccupiedEntry<'w, 'a, T>),
|
||||
Occupied(OccupiedComponentEntry<'w, 'a, T>),
|
||||
/// A vacant entry.
|
||||
Vacant(VacantEntry<'w, 'a, T>),
|
||||
Vacant(VacantComponentEntry<'w, 'a, T>),
|
||||
}
|
||||
|
||||
impl<'w, 'a, T: Component<Mutability = Mutable>> Entry<'w, 'a, T> {
|
||||
impl<'w, 'a, T: Component<Mutability = Mutable>> ComponentEntry<'w, 'a, T> {
|
||||
/// Provides in-place mutable access to an occupied entry.
|
||||
///
|
||||
/// # Examples
|
||||
@ -2885,17 +2883,17 @@ impl<'w, 'a, T: Component<Mutability = Mutable>> Entry<'w, 'a, T> {
|
||||
#[inline]
|
||||
pub fn and_modify<F: FnOnce(Mut<'_, T>)>(self, f: F) -> Self {
|
||||
match self {
|
||||
Entry::Occupied(mut entry) => {
|
||||
ComponentEntry::Occupied(mut entry) => {
|
||||
f(entry.get_mut());
|
||||
Entry::Occupied(entry)
|
||||
ComponentEntry::Occupied(entry)
|
||||
}
|
||||
Entry::Vacant(entry) => Entry::Vacant(entry),
|
||||
ComponentEntry::Vacant(entry) => ComponentEntry::Vacant(entry),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
|
||||
/// Replaces the component of the entry, and returns an [`OccupiedEntry`].
|
||||
impl<'w, 'a, T: Component> ComponentEntry<'w, 'a, T> {
|
||||
/// Replaces the component of the entry, and returns an [`OccupiedComponentEntry`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
@ -2914,13 +2912,13 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
|
||||
/// assert_eq!(entry.get(), &Comp(2));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn insert_entry(self, component: T) -> OccupiedEntry<'w, 'a, T> {
|
||||
pub fn insert_entry(self, component: T) -> OccupiedComponentEntry<'w, 'a, T> {
|
||||
match self {
|
||||
Entry::Occupied(mut entry) => {
|
||||
ComponentEntry::Occupied(mut entry) => {
|
||||
entry.insert(component);
|
||||
entry
|
||||
}
|
||||
Entry::Vacant(entry) => entry.insert(component),
|
||||
ComponentEntry::Vacant(entry) => entry.insert(component),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2946,10 +2944,10 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
|
||||
/// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 8);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn or_insert(self, default: T) -> OccupiedEntry<'w, 'a, T> {
|
||||
pub fn or_insert(self, default: T) -> OccupiedComponentEntry<'w, 'a, T> {
|
||||
match self {
|
||||
Entry::Occupied(entry) => entry,
|
||||
Entry::Vacant(entry) => entry.insert(default),
|
||||
ComponentEntry::Occupied(entry) => entry,
|
||||
ComponentEntry::Vacant(entry) => entry.insert(default),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2970,15 +2968,15 @@ impl<'w, 'a, T: Component> Entry<'w, 'a, T> {
|
||||
/// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 4);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn or_insert_with<F: FnOnce() -> T>(self, default: F) -> OccupiedEntry<'w, 'a, T> {
|
||||
pub fn or_insert_with<F: FnOnce() -> T>(self, default: F) -> OccupiedComponentEntry<'w, 'a, T> {
|
||||
match self {
|
||||
Entry::Occupied(entry) => entry,
|
||||
Entry::Vacant(entry) => entry.insert(default()),
|
||||
ComponentEntry::Occupied(entry) => entry,
|
||||
ComponentEntry::Vacant(entry) => entry.insert(default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 'a, T: Component + Default> Entry<'w, 'a, T> {
|
||||
impl<'w, 'a, T: Component + Default> ComponentEntry<'w, 'a, T> {
|
||||
/// Ensures the entry has this component by inserting the default value if empty, and
|
||||
/// returns a mutable reference to this component in the entry.
|
||||
///
|
||||
@ -2996,42 +2994,42 @@ impl<'w, 'a, T: Component + Default> Entry<'w, 'a, T> {
|
||||
/// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn or_default(self) -> OccupiedEntry<'w, 'a, T> {
|
||||
pub fn or_default(self) -> OccupiedComponentEntry<'w, 'a, T> {
|
||||
match self {
|
||||
Entry::Occupied(entry) => entry,
|
||||
Entry::Vacant(entry) => entry.insert(Default::default()),
|
||||
ComponentEntry::Occupied(entry) => entry,
|
||||
ComponentEntry::Vacant(entry) => entry.insert(Default::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into an occupied entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum.
|
||||
/// A view into an occupied entry in a [`EntityWorldMut`]. It is part of the [`OccupiedComponentEntry`] enum.
|
||||
///
|
||||
/// The contained entity must have the component type parameter if we have this struct.
|
||||
pub struct OccupiedEntry<'w, 'a, T: Component> {
|
||||
pub struct OccupiedComponentEntry<'w, 'a, T: Component> {
|
||||
entity_world: &'a mut EntityWorldMut<'w>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
|
||||
impl<'w, 'a, T: Component> OccupiedComponentEntry<'w, 'a, T> {
|
||||
/// Gets a reference to the component in the entry.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::Entry};
|
||||
/// # use bevy_ecs::{prelude::*, world::ComponentEntry};
|
||||
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Comp(u32);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// let mut entity = world.spawn(Comp(5));
|
||||
///
|
||||
/// if let Entry::Occupied(o) = entity.entry::<Comp>() {
|
||||
/// if let ComponentEntry::Occupied(o) = entity.entry::<Comp>() {
|
||||
/// assert_eq!(o.get().0, 5);
|
||||
/// }
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get(&self) -> &T {
|
||||
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
|
||||
// This shouldn't panic because if we have an OccupiedComponentEntry the component must exist.
|
||||
self.entity_world.get::<T>().unwrap()
|
||||
}
|
||||
|
||||
@ -3040,14 +3038,14 @@ impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::Entry};
|
||||
/// # use bevy_ecs::{prelude::*, world::ComponentEntry};
|
||||
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Comp(u32);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// let mut entity = world.spawn(Comp(5));
|
||||
///
|
||||
/// if let Entry::Occupied(mut o) = entity.entry::<Comp>() {
|
||||
/// if let ComponentEntry::Occupied(mut o) = entity.entry::<Comp>() {
|
||||
/// o.insert(Comp(10));
|
||||
/// }
|
||||
///
|
||||
@ -3063,14 +3061,14 @@ impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::Entry};
|
||||
/// # use bevy_ecs::{prelude::*, world::ComponentEntry};
|
||||
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Comp(u32);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// let mut entity = world.spawn(Comp(5));
|
||||
///
|
||||
/// if let Entry::Occupied(o) = entity.entry::<Comp>() {
|
||||
/// if let ComponentEntry::Occupied(o) = entity.entry::<Comp>() {
|
||||
/// assert_eq!(o.take(), Comp(5));
|
||||
/// }
|
||||
///
|
||||
@ -3078,30 +3076,30 @@ impl<'w, 'a, T: Component> OccupiedEntry<'w, 'a, T> {
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn take(self) -> T {
|
||||
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
|
||||
// This shouldn't panic because if we have an OccupiedComponentEntry the component must exist.
|
||||
self.entity_world.take().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 'a, T: Component<Mutability = Mutable>> OccupiedEntry<'w, 'a, T> {
|
||||
impl<'w, 'a, T: Component<Mutability = Mutable>> OccupiedComponentEntry<'w, 'a, T> {
|
||||
/// Gets a mutable reference to the component in the entry.
|
||||
///
|
||||
/// If you need a reference to the `OccupiedEntry` which may outlive the destruction of
|
||||
/// the `Entry` value, see [`into_mut`].
|
||||
/// If you need a reference to the [`OccupiedComponentEntry`] which may outlive the destruction of
|
||||
/// the [`OccupiedComponentEntry`] value, see [`into_mut`].
|
||||
///
|
||||
/// [`into_mut`]: Self::into_mut
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::Entry};
|
||||
/// # use bevy_ecs::{prelude::*, world::ComponentEntry};
|
||||
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Comp(u32);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// let mut entity = world.spawn(Comp(5));
|
||||
///
|
||||
/// if let Entry::Occupied(mut o) = entity.entry::<Comp>() {
|
||||
/// if let ComponentEntry::Occupied(mut o) = entity.entry::<Comp>() {
|
||||
/// o.get_mut().0 += 10;
|
||||
/// assert_eq!(o.get().0, 15);
|
||||
///
|
||||
@ -3113,28 +3111,28 @@ impl<'w, 'a, T: Component<Mutability = Mutable>> OccupiedEntry<'w, 'a, T> {
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> Mut<'_, T> {
|
||||
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
|
||||
// This shouldn't panic because if we have an OccupiedComponentEntry the component must exist.
|
||||
self.entity_world.get_mut::<T>().unwrap()
|
||||
}
|
||||
|
||||
/// Converts the `OccupiedEntry` into a mutable reference to the value in the entry with
|
||||
/// Converts the [`OccupiedComponentEntry`] into a mutable reference to the value in the entry with
|
||||
/// a lifetime bound to the `EntityWorldMut`.
|
||||
///
|
||||
/// If you need multiple references to the `OccupiedEntry`, see [`get_mut`].
|
||||
/// If you need multiple references to the [`OccupiedComponentEntry`], see [`get_mut`].
|
||||
///
|
||||
/// [`get_mut`]: Self::get_mut
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::Entry};
|
||||
/// # use bevy_ecs::{prelude::*, world::ComponentEntry};
|
||||
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Comp(u32);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// let mut entity = world.spawn(Comp(5));
|
||||
///
|
||||
/// if let Entry::Occupied(o) = entity.entry::<Comp>() {
|
||||
/// if let ComponentEntry::Occupied(o) = entity.entry::<Comp>() {
|
||||
/// o.into_mut().0 += 10;
|
||||
/// }
|
||||
///
|
||||
@ -3142,40 +3140,40 @@ impl<'w, 'a, T: Component<Mutability = Mutable>> OccupiedEntry<'w, 'a, T> {
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn into_mut(self) -> Mut<'a, T> {
|
||||
// This shouldn't panic because if we have an OccupiedEntry the component must exist.
|
||||
// This shouldn't panic because if we have an OccupiedComponentEntry the component must exist.
|
||||
self.entity_world.get_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into a vacant entry in a [`EntityWorldMut`]. It is part of the [`Entry`] enum.
|
||||
pub struct VacantEntry<'w, 'a, T: Component> {
|
||||
/// A view into a vacant entry in a [`EntityWorldMut`]. It is part of the [`ComponentEntry`] enum.
|
||||
pub struct VacantComponentEntry<'w, 'a, T: Component> {
|
||||
entity_world: &'a mut EntityWorldMut<'w>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
|
||||
/// Inserts the component into the `VacantEntry` and returns an `OccupiedEntry`.
|
||||
impl<'w, 'a, T: Component> VacantComponentEntry<'w, 'a, T> {
|
||||
/// Inserts the component into the [`VacantComponentEntry`] and returns an [`OccupiedComponentEntry`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::Entry};
|
||||
/// # use bevy_ecs::{prelude::*, world::ComponentEntry};
|
||||
/// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)]
|
||||
/// struct Comp(u32);
|
||||
///
|
||||
/// # let mut world = World::new();
|
||||
/// let mut entity = world.spawn_empty();
|
||||
///
|
||||
/// if let Entry::Vacant(v) = entity.entry::<Comp>() {
|
||||
/// if let ComponentEntry::Vacant(v) = entity.entry::<Comp>() {
|
||||
/// v.insert(Comp(10));
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 10);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn insert(self, component: T) -> OccupiedEntry<'w, 'a, T> {
|
||||
pub fn insert(self, component: T) -> OccupiedComponentEntry<'w, 'a, T> {
|
||||
self.entity_world.insert(component);
|
||||
OccupiedEntry {
|
||||
OccupiedComponentEntry {
|
||||
entity_world: self.entity_world,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
@ -3186,7 +3184,7 @@ impl<'w, 'a, T: Component> VacantEntry<'w, 'a, T> {
|
||||
///
|
||||
/// To define the access when used as a [`QueryData`](crate::query::QueryData),
|
||||
/// use a [`QueryBuilder`](crate::query::QueryBuilder) or [`QueryParamBuilder`](crate::system::QueryParamBuilder).
|
||||
/// The `FilteredEntityRef` must be the entire `QueryData`, and not nested inside a tuple with other data.
|
||||
/// The [`FilteredEntityRef`] must be the entire [`QueryData`](crate::query::QueryData), and not nested inside a tuple with other data.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::{prelude::*, world::FilteredEntityRef};
|
||||
@ -4756,7 +4754,8 @@ mod tests {
|
||||
use core::panic::AssertUnwindSafe;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::component::{HookContext, Tick};
|
||||
use crate::component::Tick;
|
||||
use crate::lifecycle::HookContext;
|
||||
use crate::{
|
||||
change_detection::{MaybeLocation, MutUntyped},
|
||||
component::ComponentId,
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
//! Defines the [`World`] and APIs for accessing it directly.
|
||||
|
||||
pub(crate) mod command_queue;
|
||||
mod component_constants;
|
||||
mod deferred_world;
|
||||
mod entity_fetch;
|
||||
mod entity_ref;
|
||||
@ -14,18 +13,22 @@ pub mod unsafe_world_cell;
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
pub mod reflect;
|
||||
|
||||
use crate::error::{DefaultErrorHandler, ErrorHandler};
|
||||
pub use crate::{
|
||||
change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD},
|
||||
world::command_queue::CommandQueue,
|
||||
};
|
||||
use crate::{
|
||||
error::{DefaultErrorHandler, ErrorHandler},
|
||||
lifecycle::{ComponentHooks, ON_ADD, ON_DESPAWN, ON_INSERT, ON_REMOVE, ON_REPLACE},
|
||||
prelude::{OnAdd, OnDespawn, OnInsert, OnRemove, OnReplace},
|
||||
};
|
||||
pub use bevy_ecs_macros::FromWorld;
|
||||
pub use component_constants::*;
|
||||
pub use deferred_world::DeferredWorld;
|
||||
pub use entity_fetch::{EntityFetcher, WorldEntityFetch};
|
||||
pub use entity_ref::{
|
||||
DynamicComponentFetch, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept, EntityWorldMut,
|
||||
Entry, FilteredEntityMut, FilteredEntityRef, OccupiedEntry, TryFromFilteredError, VacantEntry,
|
||||
ComponentEntry, DynamicComponentFetch, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
|
||||
EntityWorldMut, FilteredEntityMut, FilteredEntityRef, OccupiedComponentEntry,
|
||||
TryFromFilteredError, VacantComponentEntry,
|
||||
};
|
||||
pub use filtered_resource::*;
|
||||
pub use identifier::WorldId;
|
||||
@ -39,17 +42,17 @@ use crate::{
|
||||
},
|
||||
change_detection::{MaybeLocation, MutUntyped, TicksMut},
|
||||
component::{
|
||||
Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentIds, ComponentInfo,
|
||||
CheckChangeTicks, Component, ComponentDescriptor, ComponentId, ComponentIds, ComponentInfo,
|
||||
ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable,
|
||||
RequiredComponents, RequiredComponentsError, Tick,
|
||||
},
|
||||
entity::{Entities, Entity, EntityDoesNotExistError},
|
||||
entity_disabling::DefaultQueryFilters,
|
||||
event::{Event, EventId, Events, SendBatchIds},
|
||||
lifecycle::RemovedComponentEvents,
|
||||
observer::Observers,
|
||||
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
|
||||
relationship::RelationshipHookMode,
|
||||
removal_detection::RemovedComponentEvents,
|
||||
resource::Resource,
|
||||
schedule::{Schedule, ScheduleLabel, Schedules},
|
||||
storage::{ResourceData, Storages},
|
||||
@ -1444,7 +1447,7 @@ impl World {
|
||||
/// assert!(!transform.is_changed());
|
||||
/// ```
|
||||
///
|
||||
/// [`RemovedComponents`]: crate::removal_detection::RemovedComponents
|
||||
/// [`RemovedComponents`]: crate::lifecycle::RemovedComponents
|
||||
pub fn clear_trackers(&mut self) {
|
||||
self.removed_components.update();
|
||||
self.last_change_tick = self.increment_change_tick();
|
||||
@ -2964,6 +2967,8 @@ impl World {
|
||||
schedules.check_change_ticks(change_tick);
|
||||
}
|
||||
|
||||
self.trigger(CheckChangeTicks(change_tick));
|
||||
|
||||
self.last_check_tick = change_tick;
|
||||
}
|
||||
|
||||
|
||||
@ -8,10 +8,10 @@ use crate::{
|
||||
component::{ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick, TickCells},
|
||||
entity::{ContainsEntity, Entities, Entity, EntityDoesNotExistError, EntityLocation},
|
||||
error::{DefaultErrorHandler, ErrorHandler},
|
||||
lifecycle::RemovedComponentEvents,
|
||||
observer::Observers,
|
||||
prelude::Component,
|
||||
query::{DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||
removal_detection::RemovedComponentEvents,
|
||||
resource::Resource,
|
||||
storage::{ComponentSparseSet, Storages, Table},
|
||||
world::RawCommandQueue,
|
||||
@ -36,7 +36,7 @@ use thiserror::Error;
|
||||
///
|
||||
/// This alone is not enough to implement bevy systems where multiple systems can access *disjoint* parts of the world concurrently. For this, bevy stores all values of
|
||||
/// resources and components (and [`ComponentTicks`]) in [`UnsafeCell`]s, and carefully validates disjoint access patterns using
|
||||
/// APIs like [`System::component_access`](crate::system::System::component_access).
|
||||
/// APIs like [`System::component_access_set`](crate::system::System::component_access_set).
|
||||
///
|
||||
/// A system then can be executed using [`System::run_unsafe`](crate::system::System::run_unsafe) with a `&World` and use methods with interior mutability to access resource values.
|
||||
///
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Contains the [`AutoFocus`] component and related machinery.
|
||||
|
||||
use bevy_ecs::{component::HookContext, prelude::*, world::DeferredWorld};
|
||||
use bevy_ecs::{lifecycle::HookContext, prelude::*, world::DeferredWorld};
|
||||
|
||||
use crate::InputFocus;
|
||||
|
||||
|
||||
@ -369,7 +369,7 @@ mod tests {
|
||||
|
||||
use alloc::string::String;
|
||||
use bevy_ecs::{
|
||||
component::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld,
|
||||
lifecycle::HookContext, observer::Trigger, system::RunSystemOnce, world::DeferredWorld,
|
||||
};
|
||||
use bevy_input::{
|
||||
keyboard::{Key, KeyCode},
|
||||
|
||||
@ -395,6 +395,7 @@ bevy_color = { path = "../bevy_color", optional = true, version = "0.16.0-dev",
|
||||
"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" }
|
||||
|
||||
@ -29,6 +29,8 @@ pub use bevy_audio as audio;
|
||||
pub use bevy_color as color;
|
||||
#[cfg(feature = "bevy_core_pipeline")]
|
||||
pub use bevy_core_pipeline as core_pipeline;
|
||||
#[cfg(feature = "bevy_core_widgets")]
|
||||
pub use bevy_core_widgets as core_widgets;
|
||||
#[cfg(feature = "bevy_dev_tools")]
|
||||
pub use bevy_dev_tools as dev_tools;
|
||||
pub use bevy_diagnostic as diagnostic;
|
||||
|
||||
@ -45,7 +45,7 @@ bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = fa
|
||||
] }
|
||||
|
||||
[target.'cfg(target_os = "ios")'.dependencies]
|
||||
tracing-oslog = "0.2"
|
||||
tracing-oslog = "0.3"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -37,9 +37,9 @@ use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
lifecycle::RemovedComponents,
|
||||
query::{Changed, Or},
|
||||
reflect::ReflectComponent,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
system::{Query, Res, ResMut},
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
//!
|
||||
//! The events this module defines fall into a few broad categories:
|
||||
//! + Hovering and movement: [`Over`], [`Move`], and [`Out`].
|
||||
//! + Clicking and pressing: [`Pressed`], [`Released`], and [`Click`].
|
||||
//! + Clicking and pressing: [`Press`], [`Release`], and [`Click`].
|
||||
//! + Dragging and dropping: [`DragStart`], [`Drag`], [`DragEnd`], [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`].
|
||||
//!
|
||||
//! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains
|
||||
@ -171,7 +171,7 @@ pub struct Out {
|
||||
/// Fires when a pointer button is pressed over the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Clone, PartialEq)]
|
||||
pub struct Pressed {
|
||||
pub struct Press {
|
||||
/// Pointer button pressed to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// Information about the picking intersection.
|
||||
@ -181,7 +181,7 @@ pub struct Pressed {
|
||||
/// Fires when a pointer button is released over the `target` entity.
|
||||
#[derive(Clone, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Clone, PartialEq)]
|
||||
pub struct Released {
|
||||
pub struct Release {
|
||||
/// Pointer button lifted to trigger this event.
|
||||
pub button: PointerButton,
|
||||
/// Information about the picking intersection.
|
||||
@ -400,7 +400,7 @@ impl PointerState {
|
||||
pub struct PickingEventWriters<'w> {
|
||||
cancel_events: EventWriter<'w, Pointer<Cancel>>,
|
||||
click_events: EventWriter<'w, Pointer<Click>>,
|
||||
pressed_events: EventWriter<'w, Pointer<Pressed>>,
|
||||
pressed_events: EventWriter<'w, Pointer<Press>>,
|
||||
drag_drop_events: EventWriter<'w, Pointer<DragDrop>>,
|
||||
drag_end_events: EventWriter<'w, Pointer<DragEnd>>,
|
||||
drag_enter_events: EventWriter<'w, Pointer<DragEnter>>,
|
||||
@ -412,7 +412,7 @@ pub struct PickingEventWriters<'w> {
|
||||
move_events: EventWriter<'w, Pointer<Move>>,
|
||||
out_events: EventWriter<'w, Pointer<Out>>,
|
||||
over_events: EventWriter<'w, Pointer<Over>>,
|
||||
released_events: EventWriter<'w, Pointer<Released>>,
|
||||
released_events: EventWriter<'w, Pointer<Release>>,
|
||||
}
|
||||
|
||||
/// Dispatches interaction events to the target entities.
|
||||
@ -422,7 +422,7 @@ pub struct PickingEventWriters<'w> {
|
||||
/// + [`DragEnter`] → [`Over`].
|
||||
/// + Any number of any of the following:
|
||||
/// + For each movement: [`DragStart`] → [`Drag`] → [`DragOver`] → [`Move`].
|
||||
/// + For each button press: [`Pressed`] or [`Click`] → [`Released`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`].
|
||||
/// + For each button press: [`Press`] or [`Click`] → [`Release`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`].
|
||||
/// + For each pointer cancellation: [`Cancel`].
|
||||
///
|
||||
/// Additionally, across multiple frames, the following are also strictly
|
||||
@ -430,7 +430,7 @@ pub struct PickingEventWriters<'w> {
|
||||
/// + When a pointer moves over the target:
|
||||
/// [`Over`], [`Move`], [`Out`].
|
||||
/// + When a pointer presses buttons on the target:
|
||||
/// [`Pressed`], [`Click`], [`Released`].
|
||||
/// [`Press`], [`Click`], [`Release`].
|
||||
/// + When a pointer drags the target:
|
||||
/// [`DragStart`], [`Drag`], [`DragEnd`].
|
||||
/// + When a pointer drags something over the target:
|
||||
@ -452,7 +452,7 @@ pub struct PickingEventWriters<'w> {
|
||||
/// In the context of UI, this is especially problematic. Additional hierarchy-aware
|
||||
/// events will be added in a future release.
|
||||
///
|
||||
/// Both [`Click`] and [`Released`] target the entity hovered in the *previous frame*,
|
||||
/// Both [`Click`] and [`Release`] target the entity hovered in the *previous frame*,
|
||||
/// rather than the current frame. This is because touch pointers hover nothing
|
||||
/// on the frame they are released. The end effect is that these two events can
|
||||
/// be received sequentially after an [`Out`] event (but always on the same frame
|
||||
@ -609,7 +609,7 @@ pub fn pointer_events(
|
||||
pointer_id,
|
||||
location.clone(),
|
||||
hovered_entity,
|
||||
Pressed {
|
||||
Press {
|
||||
button,
|
||||
hit: hit.clone(),
|
||||
},
|
||||
@ -646,12 +646,12 @@ pub fn pointer_events(
|
||||
commands.trigger_targets(click_event.clone(), hovered_entity);
|
||||
event_writers.click_events.write(click_event);
|
||||
}
|
||||
// Always send the Released event
|
||||
// Always send the Release event
|
||||
let released_event = Pointer::new(
|
||||
pointer_id,
|
||||
location.clone(),
|
||||
hovered_entity,
|
||||
Released {
|
||||
Release {
|
||||
button,
|
||||
hit: hit.clone(),
|
||||
},
|
||||
|
||||
@ -14,7 +14,7 @@ use crate::{
|
||||
};
|
||||
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::{entity::EntityHashSet, prelude::*};
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::prelude::*;
|
||||
@ -279,3 +279,285 @@ fn merge_interaction_states(
|
||||
new_interaction_state.insert(*hovered_entity, new_interaction);
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that allows users to use regular Bevy change detection to determine when the pointer
|
||||
/// enters or leaves an entity. Users should insert this component on an entity to indicate interest
|
||||
/// in knowing about hover state changes.
|
||||
///
|
||||
/// The component's boolean value will be `true` whenever the pointer is currently directly hovering
|
||||
/// over the entity, or any of the entity's descendants (as defined by the [`ChildOf`]
|
||||
/// relationship). This is consistent with the behavior of the CSS `:hover` pseudo-class, which
|
||||
/// applies to the element and all of its descendants.
|
||||
///
|
||||
/// The contained boolean value is guaranteed to only be mutated when the pointer enters or leaves
|
||||
/// the entity, allowing Bevy change detection to be used efficiently. This is in contrast to the
|
||||
/// [`HoverMap`] resource, which is updated every frame.
|
||||
///
|
||||
/// Typically, a simple hoverable entity or widget will have this component added to it. More
|
||||
/// complex widgets can have this component added to each hoverable part.
|
||||
///
|
||||
/// The computational cost of keeping the `Hovered` components up to date is relatively cheap, and
|
||||
/// linear in the number of entities that have the [`Hovered`] component inserted.
|
||||
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Component, Default, PartialEq, Debug, Clone)]
|
||||
#[component(immutable)]
|
||||
pub struct Hovered(pub bool);
|
||||
|
||||
impl Hovered {
|
||||
/// Get whether the entity is currently hovered.
|
||||
pub fn get(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A component that allows users to use regular Bevy change detection to determine when the pointer
|
||||
/// is directly hovering over an entity. Users should insert this component on an entity to indicate
|
||||
/// interest in knowing about hover state changes.
|
||||
///
|
||||
/// This is similar to [`Hovered`] component, except that it does not include descendants in the
|
||||
/// hover state.
|
||||
#[derive(Component, Copy, Clone, Default, Eq, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Component, Default, PartialEq, Debug, Clone)]
|
||||
#[component(immutable)]
|
||||
pub struct DirectlyHovered(pub bool);
|
||||
|
||||
impl DirectlyHovered {
|
||||
/// Get whether the entity is currently hovered.
|
||||
pub fn get(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses [`HoverMap`] changes to update [`Hovered`] components.
|
||||
pub fn update_is_hovered(
|
||||
hover_map: Option<Res<HoverMap>>,
|
||||
mut hovers: Query<(Entity, &Hovered)>,
|
||||
parent_query: Query<&ChildOf>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// Don't do any work if there's no hover map.
|
||||
let Some(hover_map) = hover_map else { return };
|
||||
|
||||
// Don't bother collecting ancestors if there are no hovers.
|
||||
if hovers.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Algorithm: for each entity having a `Hovered` component, we want to know if the current
|
||||
// entry in the hover map is "within" (that is, in the set of descenants of) that entity. Rather
|
||||
// than doing an expensive breadth-first traversal of children, instead start with the hovermap
|
||||
// entry and search upwards. We can make this even cheaper by building a set of ancestors for
|
||||
// the hovermap entry, and then testing each `Hovered` entity against that set.
|
||||
|
||||
// A set which contains the hovered for the current pointer entity and its ancestors. The
|
||||
// capacity is based on the likely tree depth of the hierarchy, which is typically greater for
|
||||
// UI (because of layout issues) than for 3D scenes. A depth of 32 is a reasonable upper bound
|
||||
// for most use cases.
|
||||
let mut hover_ancestors = EntityHashSet::with_capacity(32);
|
||||
if let Some(map) = hover_map.get(&PointerId::Mouse) {
|
||||
for hovered_entity in map.keys() {
|
||||
hover_ancestors.insert(*hovered_entity);
|
||||
hover_ancestors.extend(parent_query.iter_ancestors(*hovered_entity));
|
||||
}
|
||||
}
|
||||
|
||||
// For each hovered entity, it is considered "hovering" if it's in the set of hovered ancestors.
|
||||
for (entity, hoverable) in hovers.iter_mut() {
|
||||
let is_hovering = hover_ancestors.contains(&entity);
|
||||
if hoverable.0 != is_hovering {
|
||||
commands.entity(entity).insert(Hovered(is_hovering));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses [`HoverMap`] changes to update [`DirectlyHovered`] components.
|
||||
pub fn update_is_directly_hovered(
|
||||
hover_map: Option<Res<HoverMap>>,
|
||||
hovers: Query<(Entity, &DirectlyHovered)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// Don't do any work if there's no hover map.
|
||||
let Some(hover_map) = hover_map else { return };
|
||||
|
||||
// Don't bother collecting ancestors if there are no hovers.
|
||||
if hovers.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(map) = hover_map.get(&PointerId::Mouse) {
|
||||
// It's hovering if it's in the HoverMap.
|
||||
for (entity, hoverable) in hovers.iter() {
|
||||
let is_hovering = map.contains_key(&entity);
|
||||
if hoverable.0 != is_hovering {
|
||||
commands.entity(entity).insert(DirectlyHovered(is_hovering));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No hovered entity, reset all hovers.
|
||||
for (entity, hoverable) in hovers.iter() {
|
||||
if hoverable.0 {
|
||||
commands.entity(entity).insert(DirectlyHovered(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_render::camera::Camera;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn update_is_hovered_memoized() {
|
||||
let mut world = World::default();
|
||||
let camera = world.spawn(Camera::default()).id();
|
||||
|
||||
// Setup entities
|
||||
let hovered_child = world.spawn_empty().id();
|
||||
let hovered_entity = world.spawn(Hovered(false)).add_child(hovered_child).id();
|
||||
|
||||
// Setup hover map with hovered_entity hovered by mouse
|
||||
let mut hover_map = HoverMap::default();
|
||||
let mut entity_map = HashMap::new();
|
||||
entity_map.insert(
|
||||
hovered_child,
|
||||
HitData {
|
||||
depth: 0.0,
|
||||
camera,
|
||||
position: None,
|
||||
normal: None,
|
||||
},
|
||||
);
|
||||
hover_map.insert(PointerId::Mouse, entity_map);
|
||||
world.insert_resource(hover_map);
|
||||
|
||||
// Run the system
|
||||
assert!(world.run_system_cached(update_is_hovered).is_ok());
|
||||
|
||||
// Check to insure that the hovered entity has the Hovered component set to true
|
||||
let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
|
||||
assert!(hover.get());
|
||||
assert!(hover.is_changed());
|
||||
|
||||
// Now do it again, but don't change the hover map.
|
||||
world.increment_change_tick();
|
||||
|
||||
assert!(world.run_system_cached(update_is_hovered).is_ok());
|
||||
let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
|
||||
assert!(hover.get());
|
||||
|
||||
// Should not be changed
|
||||
// NOTE: Test doesn't work - thinks it is always changed
|
||||
// assert!(!hover.is_changed());
|
||||
|
||||
// Clear the hover map and run again.
|
||||
world.insert_resource(HoverMap::default());
|
||||
world.increment_change_tick();
|
||||
|
||||
assert!(world.run_system_cached(update_is_hovered).is_ok());
|
||||
let hover = world.entity(hovered_entity).get_ref::<Hovered>().unwrap();
|
||||
assert!(!hover.get());
|
||||
assert!(hover.is_changed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_is_hovered_direct_self() {
|
||||
let mut world = World::default();
|
||||
let camera = world.spawn(Camera::default()).id();
|
||||
|
||||
// Setup entities
|
||||
let hovered_entity = world.spawn(DirectlyHovered(false)).id();
|
||||
|
||||
// Setup hover map with hovered_entity hovered by mouse
|
||||
let mut hover_map = HoverMap::default();
|
||||
let mut entity_map = HashMap::new();
|
||||
entity_map.insert(
|
||||
hovered_entity,
|
||||
HitData {
|
||||
depth: 0.0,
|
||||
camera,
|
||||
position: None,
|
||||
normal: None,
|
||||
},
|
||||
);
|
||||
hover_map.insert(PointerId::Mouse, entity_map);
|
||||
world.insert_resource(hover_map);
|
||||
|
||||
// Run the system
|
||||
assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
|
||||
|
||||
// Check to insure that the hovered entity has the DirectlyHovered component set to true
|
||||
let hover = world
|
||||
.entity(hovered_entity)
|
||||
.get_ref::<DirectlyHovered>()
|
||||
.unwrap();
|
||||
assert!(hover.get());
|
||||
assert!(hover.is_changed());
|
||||
|
||||
// Now do it again, but don't change the hover map.
|
||||
world.increment_change_tick();
|
||||
|
||||
assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
|
||||
let hover = world
|
||||
.entity(hovered_entity)
|
||||
.get_ref::<DirectlyHovered>()
|
||||
.unwrap();
|
||||
assert!(hover.get());
|
||||
|
||||
// Should not be changed
|
||||
// NOTE: Test doesn't work - thinks it is always changed
|
||||
// assert!(!hover.is_changed());
|
||||
|
||||
// Clear the hover map and run again.
|
||||
world.insert_resource(HoverMap::default());
|
||||
world.increment_change_tick();
|
||||
|
||||
assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
|
||||
let hover = world
|
||||
.entity(hovered_entity)
|
||||
.get_ref::<DirectlyHovered>()
|
||||
.unwrap();
|
||||
assert!(!hover.get());
|
||||
assert!(hover.is_changed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_is_hovered_direct_child() {
|
||||
let mut world = World::default();
|
||||
let camera = world.spawn(Camera::default()).id();
|
||||
|
||||
// Setup entities
|
||||
let hovered_child = world.spawn_empty().id();
|
||||
let hovered_entity = world
|
||||
.spawn(DirectlyHovered(false))
|
||||
.add_child(hovered_child)
|
||||
.id();
|
||||
|
||||
// Setup hover map with hovered_entity hovered by mouse
|
||||
let mut hover_map = HoverMap::default();
|
||||
let mut entity_map = HashMap::new();
|
||||
entity_map.insert(
|
||||
hovered_child,
|
||||
HitData {
|
||||
depth: 0.0,
|
||||
camera,
|
||||
position: None,
|
||||
normal: None,
|
||||
},
|
||||
);
|
||||
hover_map.insert(PointerId::Mouse, entity_map);
|
||||
world.insert_resource(hover_map);
|
||||
|
||||
// Run the system
|
||||
assert!(world.run_system_cached(update_is_directly_hovered).is_ok());
|
||||
|
||||
// Check to insure that the DirectlyHovered component is still false
|
||||
let hover = world
|
||||
.entity(hovered_entity)
|
||||
.get_ref::<DirectlyHovered>()
|
||||
.unwrap();
|
||||
assert!(!hover.get());
|
||||
assert!(hover.is_changed());
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,6 +170,7 @@ pub mod window;
|
||||
use bevy_app::{prelude::*, PluginGroupBuilder};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::prelude::*;
|
||||
use hover::{update_is_directly_hovered, update_is_hovered};
|
||||
|
||||
/// The picking prelude.
|
||||
///
|
||||
@ -392,6 +393,7 @@ impl Plugin for PickingPlugin {
|
||||
.register_type::<Self>()
|
||||
.register_type::<Pickable>()
|
||||
.register_type::<hover::PickingInteraction>()
|
||||
.register_type::<hover::Hovered>()
|
||||
.register_type::<pointer::PointerId>()
|
||||
.register_type::<pointer::PointerLocation>()
|
||||
.register_type::<pointer::PointerPress>()
|
||||
@ -414,7 +416,7 @@ impl Plugin for InteractionPlugin {
|
||||
.init_resource::<PointerState>()
|
||||
.add_event::<Pointer<Cancel>>()
|
||||
.add_event::<Pointer<Click>>()
|
||||
.add_event::<Pointer<Pressed>>()
|
||||
.add_event::<Pointer<Press>>()
|
||||
.add_event::<Pointer<DragDrop>>()
|
||||
.add_event::<Pointer<DragEnd>>()
|
||||
.add_event::<Pointer<DragEnter>>()
|
||||
@ -425,11 +427,16 @@ impl Plugin for InteractionPlugin {
|
||||
.add_event::<Pointer<Move>>()
|
||||
.add_event::<Pointer<Out>>()
|
||||
.add_event::<Pointer<Over>>()
|
||||
.add_event::<Pointer<Released>>()
|
||||
.add_event::<Pointer<Release>>()
|
||||
.add_event::<Pointer<Scroll>>()
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
(generate_hovermap, update_interactions, pointer_events)
|
||||
(
|
||||
generate_hovermap,
|
||||
update_interactions,
|
||||
(update_is_hovered, update_is_directly_hovered),
|
||||
pointer_events,
|
||||
)
|
||||
.chain()
|
||||
.in_set(PickingSystems::Hover),
|
||||
);
|
||||
|
||||
@ -8,9 +8,9 @@ use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::EventCursor,
|
||||
hierarchy::ChildOf,
|
||||
lifecycle::RemovedComponentEntity,
|
||||
query::QueryBuilder,
|
||||
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
|
||||
removal_detection::RemovedComponentEntity,
|
||||
system::{In, Local},
|
||||
world::{EntityRef, EntityWorldMut, FilteredEntityRef, World},
|
||||
};
|
||||
|
||||
@ -22,9 +22,10 @@ use bevy_asset::{AssetEvent, AssetId, Assets, Handle};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
change_detection::DetectChanges,
|
||||
component::{Component, HookContext},
|
||||
component::Component,
|
||||
entity::{ContainsEntity, Entity},
|
||||
event::EventReader,
|
||||
lifecycle::HookContext,
|
||||
prelude::With,
|
||||
query::Has,
|
||||
reflect::ReflectComponent,
|
||||
@ -715,12 +716,13 @@ impl Camera {
|
||||
}
|
||||
}
|
||||
|
||||
/// Control how this camera outputs once rendering is completed.
|
||||
/// Control how this [`Camera`] outputs once rendering is completed.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CameraOutputMode {
|
||||
/// Writes the camera output to configured render target.
|
||||
Write {
|
||||
/// The blend state that will be used by the pipeline that writes the intermediate render textures to the final render target texture.
|
||||
/// If not set, the output will be written as-is, ignoring `clear_color` and the existing data in the final render target texture.
|
||||
blend_state: Option<BlendState>,
|
||||
/// The clear color operation to perform on the final render target texture.
|
||||
clear_color: ClearColorConfig,
|
||||
|
||||
@ -6,7 +6,9 @@ use bevy_reflect::prelude::*;
|
||||
use derive_more::derive::From;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// For a camera, specifies the color used to clear the viewport before rendering.
|
||||
/// For a camera, specifies the color used to clear the viewport
|
||||
/// [before rendering](crate::camera::Camera::clear_color)
|
||||
/// or when [writing to the final render target texture](crate::camera::Camera::output_mode).
|
||||
#[derive(Reflect, Serialize, Deserialize, Copy, Clone, Debug, Default, From)]
|
||||
#[reflect(Serialize, Deserialize, Default, Clone)]
|
||||
pub enum ClearColorConfig {
|
||||
@ -21,10 +23,15 @@ pub enum ClearColorConfig {
|
||||
None,
|
||||
}
|
||||
|
||||
/// A [`Resource`] that stores the color that is used to clear the screen between frames.
|
||||
/// A [`Resource`] that stores the default color that cameras use to clear the screen between frames.
|
||||
///
|
||||
/// This color appears as the "background" color for simple apps,
|
||||
/// when there are portions of the screen with nothing rendered.
|
||||
///
|
||||
/// Individual cameras may use [`Camera.clear_color`] to specify a different
|
||||
/// clear color or opt out of clearing their viewport.
|
||||
///
|
||||
/// [`Camera.clear_color`]: crate::camera::Camera::clear_color
|
||||
#[derive(Resource, Clone, Debug, Deref, DerefMut, ExtractResource, Reflect)]
|
||||
#[reflect(Resource, Default, Debug, Clone)]
|
||||
pub struct ClearColor(pub Color);
|
||||
|
||||
@ -78,6 +78,9 @@ pub struct MeshAllocator {
|
||||
/// WebGL 2. On this platform, we must give each vertex array its own
|
||||
/// buffer, because we can't adjust the first vertex when we perform a draw.
|
||||
general_vertex_slabs_supported: bool,
|
||||
|
||||
/// Additional buffer usages to add to any vertex or index buffers created.
|
||||
pub extra_buffer_usages: BufferUsages,
|
||||
}
|
||||
|
||||
/// Tunable parameters that customize the behavior of the allocator.
|
||||
@ -348,6 +351,7 @@ impl FromWorld for MeshAllocator {
|
||||
mesh_id_to_index_slab: HashMap::default(),
|
||||
next_slab_id: default(),
|
||||
general_vertex_slabs_supported,
|
||||
extra_buffer_usages: BufferUsages::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -598,7 +602,7 @@ impl MeshAllocator {
|
||||
buffer_usages_to_str(buffer_usages)
|
||||
)),
|
||||
size: len as u64,
|
||||
usage: buffer_usages | BufferUsages::COPY_DST,
|
||||
usage: buffer_usages | BufferUsages::COPY_DST | self.extra_buffer_usages,
|
||||
mapped_at_creation: true,
|
||||
});
|
||||
{
|
||||
@ -835,7 +839,7 @@ impl MeshAllocator {
|
||||
buffer_usages_to_str(buffer_usages)
|
||||
)),
|
||||
size: slab.current_slot_capacity as u64 * slab.element_layout.slot_size(),
|
||||
usage: buffer_usages,
|
||||
usage: buffer_usages | self.extra_buffer_usages,
|
||||
mapped_at_creation: false,
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
use bevy_app::Plugin;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::entity::EntityHash;
|
||||
use bevy_ecs::lifecycle::{OnAdd, OnRemove};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::{ContainsEntity, Entity, EntityEquivalent},
|
||||
@ -9,7 +10,7 @@ use bevy_ecs::{
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
system::{Local, Query, ResMut, SystemState},
|
||||
world::{Mut, OnAdd, OnRemove, World},
|
||||
world::{Mut, World},
|
||||
};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
@ -219,10 +220,10 @@ pub(crate) fn entity_sync_system(main_world: &mut World, render_world: &mut Worl
|
||||
EntityRecord::Added(e) => {
|
||||
if let Ok(mut main_entity) = world.get_entity_mut(e) {
|
||||
match main_entity.entry::<RenderEntity>() {
|
||||
bevy_ecs::world::Entry::Occupied(_) => {
|
||||
bevy_ecs::world::ComponentEntry::Occupied(_) => {
|
||||
panic!("Attempting to synchronize an entity that has already been synchronized!");
|
||||
}
|
||||
bevy_ecs::world::Entry::Vacant(entry) => {
|
||||
bevy_ecs::world::ComponentEntry::Vacant(entry) => {
|
||||
let id = render_world.spawn(MainEntity(e)).id();
|
||||
|
||||
entry.insert(RenderEntity(id));
|
||||
@ -490,10 +491,11 @@ mod tests {
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
lifecycle::{OnAdd, OnRemove},
|
||||
observer::Trigger,
|
||||
query::With,
|
||||
system::{Query, ResMut},
|
||||
world::{OnAdd, OnRemove, World},
|
||||
world::World,
|
||||
};
|
||||
|
||||
use super::{
|
||||
|
||||
@ -3,8 +3,8 @@ mod render_layers;
|
||||
|
||||
use core::any::TypeId;
|
||||
|
||||
use bevy_ecs::component::HookContext;
|
||||
use bevy_ecs::entity::EntityHashSet;
|
||||
use bevy_ecs::lifecycle::HookContext;
|
||||
use bevy_ecs::world::DeferredWorld;
|
||||
use derive_more::derive::{Deref, DerefMut};
|
||||
pub use range::*;
|
||||
|
||||
@ -10,9 +10,9 @@ use bevy_app::{App, Plugin, PostUpdate};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::{Entity, EntityHashMap},
|
||||
lifecycle::RemovedComponents,
|
||||
query::{Changed, With},
|
||||
reflect::ReflectComponent,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs as _,
|
||||
system::{Local, Query, Res, ResMut},
|
||||
|
||||
@ -15,7 +15,12 @@ default = ["std", "async_executor"]
|
||||
|
||||
## Enables multi-threading support.
|
||||
## Without this feature, all tasks will be run on a single thread.
|
||||
multi_threaded = ["std", "dep:async-channel", "dep:concurrent-queue"]
|
||||
multi_threaded = [
|
||||
"std",
|
||||
"dep:async-channel",
|
||||
"dep:concurrent-queue",
|
||||
"async_executor",
|
||||
]
|
||||
|
||||
## Uses `async-executor` as a task execution backend.
|
||||
## This backend is incompatible with `no_std` targets.
|
||||
|
||||
@ -5,7 +5,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
/// The maximum width and height of text. The text will wrap according to the specified size.
|
||||
///
|
||||
/// Characters out of the bounds after wrapping will be truncated. Text is aligned according to the
|
||||
/// specified [`JustifyText`](crate::text::JustifyText).
|
||||
/// specified [`Justify`](crate::text::Justify).
|
||||
///
|
||||
/// Note: only characters that are completely out of the bounds will be truncated, so this is not a
|
||||
/// reliable limit if it is necessary to contain the text strictly in the bounds. Currently this
|
||||
|
||||
@ -61,7 +61,7 @@ pub use text_access::*;
|
||||
pub mod prelude {
|
||||
#[doc(hidden)]
|
||||
pub use crate::{
|
||||
Font, JustifyText, LineBreak, Text2d, Text2dReader, Text2dWriter, TextColor, TextError,
|
||||
Font, Justify, LineBreak, Text2d, Text2dReader, Text2dWriter, TextColor, TextError,
|
||||
TextFont, TextLayout, TextSpan,
|
||||
};
|
||||
}
|
||||
|
||||
@ -16,8 +16,8 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
|
||||
|
||||
use crate::{
|
||||
error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, JustifyText,
|
||||
LineBreak, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout,
|
||||
error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, Justify, LineBreak,
|
||||
PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout,
|
||||
};
|
||||
|
||||
/// A wrapper resource around a [`cosmic_text::FontSystem`]
|
||||
@ -88,7 +88,7 @@ impl TextPipeline {
|
||||
fonts: &Assets<Font>,
|
||||
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
|
||||
linebreak: LineBreak,
|
||||
justify: JustifyText,
|
||||
justify: Justify,
|
||||
bounds: TextBounds,
|
||||
scale_factor: f64,
|
||||
computed: &mut ComputedTextBlock,
|
||||
@ -201,7 +201,7 @@ impl TextPipeline {
|
||||
|
||||
// Workaround for alignment not working for unbounded text.
|
||||
// See https://github.com/pop-os/cosmic-text/issues/343
|
||||
if bounds.width.is_none() && justify != JustifyText::Left {
|
||||
if bounds.width.is_none() && justify != Justify::Left {
|
||||
let dimensions = buffer_dimensions(buffer);
|
||||
// `set_size` causes a re-layout to occur.
|
||||
buffer.set_size(font_system, Some(dimensions.x), bounds.height);
|
||||
|
||||
@ -116,19 +116,19 @@ impl Default for ComputedTextBlock {
|
||||
pub struct TextLayout {
|
||||
/// The text's internal alignment.
|
||||
/// Should not affect its position within a container.
|
||||
pub justify: JustifyText,
|
||||
pub justify: Justify,
|
||||
/// How the text should linebreak when running out of the bounds determined by `max_size`.
|
||||
pub linebreak: LineBreak,
|
||||
}
|
||||
|
||||
impl TextLayout {
|
||||
/// Makes a new [`TextLayout`].
|
||||
pub const fn new(justify: JustifyText, linebreak: LineBreak) -> Self {
|
||||
pub const fn new(justify: Justify, linebreak: LineBreak) -> Self {
|
||||
Self { justify, linebreak }
|
||||
}
|
||||
|
||||
/// Makes a new [`TextLayout`] with the specified [`JustifyText`].
|
||||
pub fn new_with_justify(justify: JustifyText) -> Self {
|
||||
/// Makes a new [`TextLayout`] with the specified [`Justify`].
|
||||
pub fn new_with_justify(justify: Justify) -> Self {
|
||||
Self::default().with_justify(justify)
|
||||
}
|
||||
|
||||
@ -143,8 +143,8 @@ impl TextLayout {
|
||||
Self::default().with_no_wrap()
|
||||
}
|
||||
|
||||
/// Returns this [`TextLayout`] with the specified [`JustifyText`].
|
||||
pub const fn with_justify(mut self, justify: JustifyText) -> Self {
|
||||
/// Returns this [`TextLayout`] with the specified [`Justify`].
|
||||
pub const fn with_justify(mut self, justify: Justify) -> Self {
|
||||
self.justify = justify;
|
||||
self
|
||||
}
|
||||
@ -246,7 +246,7 @@ impl From<String> for TextSpan {
|
||||
/// [`TextBounds`](super::bounds::TextBounds) component with an explicit `width` value.
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(Serialize, Deserialize, Clone, PartialEq, Hash)]
|
||||
pub enum JustifyText {
|
||||
pub enum Justify {
|
||||
/// Leftmost character is immediately to the right of the render position.
|
||||
/// Bounds start from the render position and advance rightwards.
|
||||
#[default]
|
||||
@ -263,13 +263,13 @@ pub enum JustifyText {
|
||||
Justified,
|
||||
}
|
||||
|
||||
impl From<JustifyText> for cosmic_text::Align {
|
||||
fn from(justify: JustifyText) -> Self {
|
||||
impl From<Justify> for cosmic_text::Align {
|
||||
fn from(justify: Justify) -> Self {
|
||||
match justify {
|
||||
JustifyText::Left => cosmic_text::Align::Left,
|
||||
JustifyText::Center => cosmic_text::Align::Center,
|
||||
JustifyText::Right => cosmic_text::Align::Right,
|
||||
JustifyText::Justified => cosmic_text::Align::Justified,
|
||||
Justify::Left => cosmic_text::Align::Left,
|
||||
Justify::Center => cosmic_text::Align::Center,
|
||||
Justify::Right => cosmic_text::Align::Right,
|
||||
Justify::Justified => cosmic_text::Align::Justified,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -354,8 +354,8 @@ impl Default for TextFont {
|
||||
/// Specifies the height of each line of text for `Text` and `Text2d`
|
||||
///
|
||||
/// Default is 1.2x the font size
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
|
||||
#[reflect(Debug, Clone, PartialEq)]
|
||||
pub enum LineHeight {
|
||||
/// Set line height to a specific number of pixels
|
||||
Px(f32),
|
||||
|
||||
@ -51,7 +51,7 @@ use bevy_window::{PrimaryWindow, Window};
|
||||
/// # use bevy_color::Color;
|
||||
/// # use bevy_color::palettes::basic::BLUE;
|
||||
/// # use bevy_ecs::world::World;
|
||||
/// # use bevy_text::{Font, JustifyText, Text2d, TextLayout, TextFont, TextColor, TextSpan};
|
||||
/// # use bevy_text::{Font, Justify, Text2d, TextLayout, TextFont, TextColor, TextSpan};
|
||||
/// #
|
||||
/// # let font_handle: Handle<Font> = Default::default();
|
||||
/// # let mut world = World::default();
|
||||
@ -73,7 +73,7 @@ use bevy_window::{PrimaryWindow, Window};
|
||||
/// // With text justification.
|
||||
/// world.spawn((
|
||||
/// Text2d::new("hello world\nand bevy!"),
|
||||
/// TextLayout::new_with_justify(JustifyText::Center)
|
||||
/// TextLayout::new_with_justify(Justify::Center)
|
||||
/// ));
|
||||
///
|
||||
/// // With spans
|
||||
|
||||
74
crates/bevy_ui/src/interaction_states.rs
Normal file
74
crates/bevy_ui/src/interaction_states.rs
Normal file
@ -0,0 +1,74 @@
|
||||
/// This module contains components that are used to track the interaction state of UI widgets.
|
||||
use bevy_a11y::AccessibilityNode;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
lifecycle::{OnAdd, OnInsert, OnRemove},
|
||||
observer::Trigger,
|
||||
world::DeferredWorld,
|
||||
};
|
||||
|
||||
/// A component indicating that a widget is disabled and should be "grayed out".
|
||||
/// This is used to prevent user interaction with the widget. It should not, however, prevent
|
||||
/// the widget from being updated or rendered, or from acquiring keyboard focus.
|
||||
///
|
||||
/// For apps which support a11y: if a widget (such as a slider) contains multiple entities,
|
||||
/// the `InteractionDisabled` component should be added to the root entity of the widget - the
|
||||
/// same entity that contains the `AccessibilityNode` component. This will ensure that
|
||||
/// the a11y tree is updated correctly.
|
||||
#[derive(Component, Debug, Clone, Copy, Default)]
|
||||
pub struct InteractionDisabled;
|
||||
|
||||
pub(crate) fn on_add_disabled(
|
||||
trigger: Trigger<OnAdd, InteractionDisabled>,
|
||||
mut world: DeferredWorld,
|
||||
) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_disabled();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_remove_disabled(
|
||||
trigger: Trigger<OnRemove, InteractionDisabled>,
|
||||
mut world: DeferredWorld,
|
||||
) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.clear_disabled();
|
||||
}
|
||||
}
|
||||
|
||||
/// Component that indicates whether a button or widget is currently in a pressed or "held down"
|
||||
/// state.
|
||||
#[derive(Component, Default, Debug)]
|
||||
pub struct Pressed;
|
||||
|
||||
/// Component that indicates whether a checkbox or radio button is in a checked state.
|
||||
#[derive(Component, Default, Debug)]
|
||||
#[component(immutable)]
|
||||
pub struct Checked(pub bool);
|
||||
|
||||
impl Checked {
|
||||
/// Returns whether the checkbox or radio button is currently checked.
|
||||
pub fn get(&self) -> bool {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_insert_is_checked(trigger: Trigger<OnInsert, Checked>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
let checked = entity.get::<Checked>().unwrap().get();
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_toggled(match checked {
|
||||
true => accesskit::Toggled::True,
|
||||
false => accesskit::Toggled::False,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn on_remove_is_checked(trigger: Trigger<OnRemove, Checked>, mut world: DeferredWorld) {
|
||||
let mut entity = world.entity_mut(trigger.target().unwrap());
|
||||
if let Some(mut accessibility) = entity.get_mut::<AccessibilityNode>() {
|
||||
accessibility.set_toggled(accesskit::Toggled::False);
|
||||
}
|
||||
}
|
||||
@ -8,8 +8,8 @@ use bevy_ecs::{
|
||||
change_detection::{DetectChanges, DetectChangesMut},
|
||||
entity::Entity,
|
||||
hierarchy::{ChildOf, Children},
|
||||
lifecycle::RemovedComponents,
|
||||
query::With,
|
||||
removal_detection::RemovedComponents,
|
||||
system::{Commands, Query, ResMut},
|
||||
world::Ref,
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
//! Spawn UI elements with [`widget::Button`], [`ImageNode`], [`Text`](prelude::Text) and [`Node`]
|
||||
//! This UI is laid out with the Flexbox and CSS Grid layout models (see <https://cssreference.io/flexbox/>)
|
||||
|
||||
pub mod interaction_states;
|
||||
pub mod measurement;
|
||||
pub mod ui_material;
|
||||
pub mod update;
|
||||
@ -38,6 +39,7 @@ mod ui_node;
|
||||
pub use focus::*;
|
||||
pub use geometry::*;
|
||||
pub use gradients::*;
|
||||
pub use interaction_states::{Checked, InteractionDisabled, Pressed};
|
||||
pub use layout::*;
|
||||
pub use measurement::*;
|
||||
pub use render::*;
|
||||
@ -319,6 +321,11 @@ fn build_text_interop(app: &mut App) {
|
||||
|
||||
app.add_plugins(accessibility::AccessibilityPlugin);
|
||||
|
||||
app.add_observer(interaction_states::on_add_disabled)
|
||||
.add_observer(interaction_states::on_remove_disabled)
|
||||
.add_observer(interaction_states::on_insert_is_checked)
|
||||
.add_observer(interaction_states::on_remove_is_checked);
|
||||
|
||||
app.configure_sets(
|
||||
PostUpdate,
|
||||
AmbiguousWithText.ambiguous_with(widget::text_system),
|
||||
|
||||
@ -2130,6 +2130,16 @@ impl BorderColor {
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to set all border colors to a given color.
|
||||
pub fn set_all(&mut self, color: impl Into<Color>) -> &mut Self {
|
||||
let color: Color = color.into();
|
||||
self.top = color;
|
||||
self.bottom = color;
|
||||
self.left = color;
|
||||
self.right = color;
|
||||
self
|
||||
}
|
||||
|
||||
/// Check if all contained border colors are transparent
|
||||
pub fn is_fully_transparent(&self) -> bool {
|
||||
self.top.is_fully_transparent()
|
||||
@ -2865,8 +2875,8 @@ impl ComputedNodeTarget {
|
||||
}
|
||||
|
||||
/// Adds a shadow behind text
|
||||
#[derive(Component, Copy, Clone, Debug, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone)]
|
||||
#[derive(Component, Copy, Clone, Debug, PartialEq, Reflect)]
|
||||
#[reflect(Component, Default, Debug, Clone, PartialEq)]
|
||||
pub struct TextShadow {
|
||||
/// Shadow displacement in logical pixels
|
||||
/// With a value of zero the shadow will be hidden directly behind the text
|
||||
|
||||
@ -138,8 +138,8 @@ impl From<Handle<Image>> for ImageNode {
|
||||
}
|
||||
|
||||
/// Controls how the image is altered to fit within the layout and how the layout algorithm determines the space in the layout for the image
|
||||
#[derive(Default, Debug, Clone, Reflect)]
|
||||
#[reflect(Clone, Default)]
|
||||
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
|
||||
#[reflect(Clone, Default, PartialEq)]
|
||||
pub enum NodeImageMode {
|
||||
/// The image will be sized automatically by taking the size of the source image and applying any layout constraints.
|
||||
#[default]
|
||||
|
||||
@ -61,7 +61,7 @@ impl Default for TextNodeFlags {
|
||||
/// # use bevy_color::Color;
|
||||
/// # use bevy_color::palettes::basic::BLUE;
|
||||
/// # use bevy_ecs::world::World;
|
||||
/// # use bevy_text::{Font, JustifyText, TextLayout, TextFont, TextColor, TextSpan};
|
||||
/// # use bevy_text::{Font, Justify, TextLayout, TextFont, TextColor, TextSpan};
|
||||
/// # use bevy_ui::prelude::Text;
|
||||
/// #
|
||||
/// # let font_handle: Handle<Font> = Default::default();
|
||||
@ -84,7 +84,7 @@ impl Default for TextNodeFlags {
|
||||
/// // With text justification.
|
||||
/// world.spawn((
|
||||
/// Text::new("hello world\nand bevy!"),
|
||||
/// TextLayout::new_with_justify(JustifyText::Center)
|
||||
/// TextLayout::new_with_justify(Justify::Center)
|
||||
/// ));
|
||||
///
|
||||
/// // With spans
|
||||
|
||||
@ -22,11 +22,12 @@ use bevy_ecs::{
|
||||
change_detection::DetectChanges,
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
lifecycle::OnRemove,
|
||||
observer::Trigger,
|
||||
query::With,
|
||||
reflect::ReflectComponent,
|
||||
system::{Commands, Local, Query},
|
||||
world::{OnRemove, Ref},
|
||||
world::Ref,
|
||||
};
|
||||
#[cfg(feature = "custom_cursor")]
|
||||
use bevy_image::{Image, TextureAtlasLayout};
|
||||
|
||||
@ -3,9 +3,9 @@ use std::collections::HashMap;
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::EventWriter,
|
||||
lifecycle::RemovedComponents,
|
||||
prelude::{Changed, Component},
|
||||
query::QueryFilter,
|
||||
removal_detection::RemovedComponents,
|
||||
system::{Local, NonSendMarker, Query, SystemParamItem},
|
||||
};
|
||||
use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput};
|
||||
|
||||
@ -21,6 +21,7 @@ The default feature set enables most of the expected features of a game engine,
|
||||
|bevy_audio|Provides audio functionality|
|
||||
|bevy_color|Provides shared color types and operations|
|
||||
|bevy_core_pipeline|Provides cameras and other basic render pipeline features|
|
||||
|bevy_core_widgets|Headless widget collection for Bevy UI.|
|
||||
|bevy_gilrs|Adds gamepad support|
|
||||
|bevy_gizmos|Adds support for rendering gizmos|
|
||||
|bevy_gltf|[glTF](https://www.khronos.org/gltf/) support|
|
||||
|
||||
@ -129,7 +129,7 @@ fn setup_sprites(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
cmd.with_children(|builder| {
|
||||
builder.spawn((
|
||||
Text2d::new(rect.text),
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
TextFont::from_font_size(15.),
|
||||
Transform::from_xyz(0., -0.5 * rect.size.y - 10., 0.),
|
||||
bevy::sprite::Anchor::TOP_CENTER,
|
||||
@ -275,7 +275,7 @@ fn setup_texture_atlas(
|
||||
cmd.with_children(|builder| {
|
||||
builder.spawn((
|
||||
Text2d::new(sprite_sheet.text),
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
TextFont::from_font_size(15.),
|
||||
Transform::from_xyz(0., -0.5 * sprite_sheet.size.y - 10., 0.),
|
||||
bevy::sprite::Anchor::TOP_CENTER,
|
||||
|
||||
@ -94,7 +94,7 @@ fn spawn_sprites(
|
||||
children![(
|
||||
Text2d::new(label),
|
||||
text_style,
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
Transform::from_xyz(0., -0.5 * size.y - 10., 0.0),
|
||||
bevy::sprite::Anchor::TOP_CENTER,
|
||||
)],
|
||||
|
||||
@ -40,7 +40,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
font_size: 50.0,
|
||||
..default()
|
||||
};
|
||||
let text_justification = JustifyText::Center;
|
||||
let text_justification = Justify::Center;
|
||||
commands.spawn(Camera2d);
|
||||
// Demonstrate changing translation
|
||||
commands.spawn((
|
||||
@ -78,7 +78,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
children | Demonstrates how to create a node with a shadow
|
||||
[Button](../examples/ui/button.rs) | Illustrates creating and updating a button
|
||||
[CSS Grid](../examples/ui/grid.rs) | An example for CSS Grid layout
|
||||
[Core Widgets](../examples/ui/core_widgets.rs) | Demonstrates use of core (headless) widgets in Bevy UI
|
||||
[Core Widgets (w/Observers)](../examples/ui/core_widgets_observers.rs) | Demonstrates use of core (headless) widgets in Bevy UI, with Observers
|
||||
[Directional Navigation](../examples/ui/directional_navigation.rs) | Demonstration of Directional Navigation between UI elements
|
||||
[Display and Visibility](../examples/ui/display_and_visibility.rs) | Demonstrates how Display and Visibility work in the UI.
|
||||
[Flex Layout](../examples/ui/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text
|
||||
|
||||
@ -151,7 +151,7 @@ fn setup(
|
||||
..default()
|
||||
},
|
||||
TextColor(Color::Srgba(Srgba::RED)),
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
))
|
||||
// Mark as an animation target.
|
||||
.insert(AnimationTarget {
|
||||
|
||||
@ -277,7 +277,7 @@ fn setup_node_rects(commands: &mut Commands) {
|
||||
..default()
|
||||
},
|
||||
TextColor(ANTIQUE_WHITE.into()),
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
))
|
||||
.id();
|
||||
|
||||
|
||||
@ -334,7 +334,7 @@ fn add_mask_group_control(
|
||||
} else {
|
||||
selected_button_text_style.clone()
|
||||
},
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
Node {
|
||||
flex_grow: 1.0,
|
||||
margin: UiRect::vertical(Val::Px(3.0)),
|
||||
|
||||
@ -54,7 +54,7 @@ fn spawn_text(mut commands: Commands, mut reader: EventReader<StreamEvent>) {
|
||||
for (per_frame, event) in reader.read().enumerate() {
|
||||
commands.spawn((
|
||||
Text2d::new(event.0.to_string()),
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
Transform::from_xyz(per_frame as f32 * 100.0, 300.0, 0.0),
|
||||
));
|
||||
}
|
||||
|
||||
@ -14,7 +14,8 @@
|
||||
//! between components (like hierarchies or parent-child links) and need to maintain correctness.
|
||||
|
||||
use bevy::{
|
||||
ecs::component::{ComponentHook, HookContext, Mutable, StorageType},
|
||||
ecs::component::{Mutable, StorageType},
|
||||
ecs::lifecycle::{ComponentHook, HookContext},
|
||||
prelude::*,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
|
||||
@ -2,9 +2,8 @@
|
||||
|
||||
use bevy::{
|
||||
ecs::{
|
||||
component::{
|
||||
ComponentCloneBehavior, ComponentDescriptor, ComponentId, HookContext, StorageType,
|
||||
},
|
||||
component::{ComponentCloneBehavior, ComponentDescriptor, ComponentId, StorageType},
|
||||
lifecycle::HookContext,
|
||||
world::DeferredWorld,
|
||||
},
|
||||
platform::collections::HashMap,
|
||||
|
||||
@ -94,7 +94,7 @@ fn setup_ui(mut commands: Commands) {
|
||||
commands
|
||||
.spawn((
|
||||
Text::default(),
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
Node {
|
||||
align_self: AlignSelf::Center,
|
||||
justify_self: JustifySelf::Center,
|
||||
|
||||
@ -198,7 +198,7 @@ fn run_camera_controller(
|
||||
}
|
||||
let cursor_grab = *mouse_cursor_grab || *toggle_cursor_grab;
|
||||
|
||||
// Apply movement update
|
||||
// Update velocity
|
||||
if axis_input != Vec3::ZERO {
|
||||
let max_speed = if key_input.pressed(controller.key_run) {
|
||||
controller.run_speed
|
||||
@ -213,11 +213,15 @@ fn run_camera_controller(
|
||||
controller.velocity = Vec3::ZERO;
|
||||
}
|
||||
}
|
||||
let forward = *transform.forward();
|
||||
let right = *transform.right();
|
||||
transform.translation += controller.velocity.x * dt * right
|
||||
+ controller.velocity.y * dt * Vec3::Y
|
||||
+ controller.velocity.z * dt * forward;
|
||||
|
||||
// Apply movement update
|
||||
if controller.velocity != Vec3::ZERO {
|
||||
let forward = *transform.forward();
|
||||
let right = *transform.right();
|
||||
transform.translation += controller.velocity.x * dt * right
|
||||
+ controller.velocity.y * dt * Vec3::Y
|
||||
+ controller.velocity.z * dt * forward;
|
||||
}
|
||||
|
||||
// Handle cursor grab
|
||||
if cursor_grab_change {
|
||||
|
||||
@ -379,7 +379,7 @@ fn setup_text(mut commands: Commands, cameras: Query<(Entity, &Camera)>) {
|
||||
children![(
|
||||
Text::default(),
|
||||
HeaderText,
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
children![
|
||||
TextSpan::new("Primitive: "),
|
||||
TextSpan(format!("{text}", text = PrimitiveSelected::default())),
|
||||
|
||||
@ -158,7 +158,7 @@ fn setup_scene(
|
||||
..default()
|
||||
},
|
||||
TextColor::BLACK,
|
||||
TextLayout::new_with_justify(JustifyText::Center),
|
||||
TextLayout::new_with_justify(Justify::Center),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@ -91,8 +91,8 @@ fn setup_scene(
|
||||
))
|
||||
.observe(update_material_on::<Pointer<Over>>(hover_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Out>>(white_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Pressed>>(pressed_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Released>>(hover_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Press>>(pressed_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Release>>(hover_matl.clone()))
|
||||
.observe(rotate_on_drag);
|
||||
}
|
||||
|
||||
@ -114,8 +114,8 @@ fn setup_scene(
|
||||
))
|
||||
.observe(update_material_on::<Pointer<Over>>(hover_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Out>>(white_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Pressed>>(pressed_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Released>>(hover_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Press>>(pressed_matl.clone()))
|
||||
.observe(update_material_on::<Pointer<Release>>(hover_matl.clone()))
|
||||
.observe(rotate_on_drag);
|
||||
}
|
||||
|
||||
|
||||
@ -63,8 +63,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
))
|
||||
.observe(recolor_on::<Pointer<Over>>(Color::srgb(0.0, 1.0, 1.0)))
|
||||
.observe(recolor_on::<Pointer<Out>>(Color::BLACK))
|
||||
.observe(recolor_on::<Pointer<Pressed>>(Color::srgb(1.0, 1.0, 0.0)))
|
||||
.observe(recolor_on::<Pointer<Released>>(Color::srgb(0.0, 1.0, 1.0)));
|
||||
.observe(recolor_on::<Pointer<Press>>(Color::srgb(1.0, 1.0, 0.0)))
|
||||
.observe(recolor_on::<Pointer<Release>>(Color::srgb(0.0, 1.0, 1.0)));
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
@ -83,8 +83,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
))
|
||||
.observe(recolor_on::<Pointer<Over>>(Color::srgb(0.0, 1.0, 0.0)))
|
||||
.observe(recolor_on::<Pointer<Out>>(Color::srgb(1.0, 0.0, 0.0)))
|
||||
.observe(recolor_on::<Pointer<Pressed>>(Color::srgb(0.0, 0.0, 1.0)))
|
||||
.observe(recolor_on::<Pointer<Released>>(Color::srgb(0.0, 1.0, 0.0)));
|
||||
.observe(recolor_on::<Pointer<Press>>(Color::srgb(0.0, 0.0, 1.0)))
|
||||
.observe(recolor_on::<Pointer<Release>>(Color::srgb(0.0, 1.0, 0.0)));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -145,8 +145,8 @@ fn setup_atlas(
|
||||
))
|
||||
.observe(recolor_on::<Pointer<Over>>(Color::srgb(0.0, 1.0, 1.0)))
|
||||
.observe(recolor_on::<Pointer<Out>>(Color::srgb(1.0, 1.0, 1.0)))
|
||||
.observe(recolor_on::<Pointer<Pressed>>(Color::srgb(1.0, 1.0, 0.0)))
|
||||
.observe(recolor_on::<Pointer<Released>>(Color::srgb(0.0, 1.0, 1.0)));
|
||||
.observe(recolor_on::<Pointer<Press>>(Color::srgb(1.0, 1.0, 0.0)))
|
||||
.observe(recolor_on::<Pointer<Release>>(Color::srgb(0.0, 1.0, 1.0)));
|
||||
}
|
||||
|
||||
// An observer listener that changes the target entity's color.
|
||||
|
||||
@ -6,11 +6,19 @@ use bevy::{
|
||||
math::ops::{cos, sin},
|
||||
prelude::*,
|
||||
render::camera::Viewport,
|
||||
window::{PresentMode, WindowResolution},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_plugins(DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
resolution: WindowResolution::new(1920.0, 1080.0).with_scale_factor_override(1.0),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, rotate_cameras)
|
||||
.run();
|
||||
|
||||
@ -74,7 +74,7 @@ fn setup(mut commands: Commands, args: Res<Args>) {
|
||||
..Default::default()
|
||||
};
|
||||
let text_block = TextLayout {
|
||||
justify: JustifyText::Left,
|
||||
justify: Justify::Left,
|
||||
linebreak: LineBreak::AnyCharacter,
|
||||
};
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ struct Args {
|
||||
#[argh(switch)]
|
||||
no_frustum_culling: bool,
|
||||
|
||||
/// whether the text should use `JustifyText::Center`.
|
||||
/// whether the text should use `Justify::Center`.
|
||||
#[argh(switch)]
|
||||
center: bool,
|
||||
}
|
||||
@ -132,9 +132,9 @@ fn setup(mut commands: Commands, font: Res<FontHandle>, args: Res<Args>) {
|
||||
random_text_font(&mut rng, &args, font.0.clone()),
|
||||
TextColor(color.into()),
|
||||
TextLayout::new_with_justify(if args.center {
|
||||
JustifyText::Center
|
||||
Justify::Center
|
||||
} else {
|
||||
JustifyText::Left
|
||||
Justify::Left
|
||||
}),
|
||||
Transform {
|
||||
translation,
|
||||
|
||||
@ -69,7 +69,7 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
.spawn((
|
||||
Text2d::default(),
|
||||
TextLayout {
|
||||
justify: JustifyText::Center,
|
||||
justify: Justify::Center,
|
||||
linebreak: LineBreak::AnyCharacter,
|
||||
},
|
||||
TextBounds::default(),
|
||||
|
||||
@ -151,10 +151,10 @@ mod text {
|
||||
commands.spawn((Camera2d, DespawnOnExitState(super::Scene::Text)));
|
||||
|
||||
for (i, justify) in [
|
||||
JustifyText::Left,
|
||||
JustifyText::Right,
|
||||
JustifyText::Center,
|
||||
JustifyText::Justified,
|
||||
Justify::Left,
|
||||
Justify::Right,
|
||||
Justify::Center,
|
||||
Justify::Justified,
|
||||
]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
@ -196,7 +196,7 @@ mod text {
|
||||
fn spawn_anchored_text(
|
||||
commands: &mut Commands,
|
||||
dest: Vec3,
|
||||
justify: JustifyText,
|
||||
justify: Justify,
|
||||
bounds: Option<TextBounds>,
|
||||
) {
|
||||
commands.spawn((
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user