Merge branch 'main' into new-example-multiple-sprites-one-entity

This commit is contained in:
Andrew 2024-06-17 20:53:20 -04:00 committed by GitHub
commit 863b0c913e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
207 changed files with 8363 additions and 2596 deletions

View file

@ -145,7 +145,7 @@ rustflags = [
# [profile.dev]
# debug = 1
# This is enables you to run the CI tool using `cargo ci`.
# This enables you to run the CI tool using `cargo ci`.
# This is not enabled by default, you need to copy this file to `config.toml`.
[alias]
ci = "run --package ci --"

View file

@ -219,7 +219,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.21.0
uses: crate-ci/typos@v1.22.7
- name: Typos info
if: failure()
run: |

View file

@ -132,7 +132,9 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@master
with:
toolchain: 1.78
- name: Build bevy
shell: bash
# this uses the same command as when running the example to ensure build is reused

View file

@ -63,6 +63,7 @@ default = [
"bevy_winit",
"bevy_core_pipeline",
"bevy_pbr",
"bevy_picking",
"bevy_gltf",
"bevy_render",
"bevy_sprite",
@ -123,6 +124,9 @@ bevy_pbr = [
"bevy_core_pipeline",
]
# Provides picking functionality
bevy_picking = ["bevy_internal/bevy_picking"]
# Provides rendering functionality
bevy_render = ["bevy_internal/bevy_render", "bevy_color"]
@ -2545,6 +2549,17 @@ description = "Systems run in parallel, but their order isn't always determinist
category = "ECS (Entity Component System)"
wasm = false
[[example]]
name = "observers"
path = "examples/ecs/observers.rs"
doc-scrape-examples = true
[package.metadata.example.observers]
name = "Observers"
description = "Demonstrates observers that react to events (both built-in life-cycle events and custom events)"
category = "ECS (Entity Component System)"
wasm = true
[[example]]
name = "3d_rotation"
path = "examples/transforms/3d_rotation.rs"
@ -3035,6 +3050,17 @@ description = "Demonstrates all the primitives which can be sampled."
category = "Math"
wasm = true
[[example]]
name = "custom_primitives"
path = "examples/math/custom_primitives.rs"
doc-scrape-examples = true
[package.metadata.example.custom_primitives]
name = "Custom Primitives"
description = "Demonstrates how to add custom primitives and useful traits for them."
category = "Math"
wasm = true
[[example]]
name = "random_sampling"
path = "examples/math/random_sampling.rs"
@ -3046,6 +3072,17 @@ description = "Demonstrates how to sample random points from mathematical primit
category = "Math"
wasm = true
[[example]]
name = "smooth_follow"
path = "examples/math/smooth_follow.rs"
doc-scrape-examples = true
[package.metadata.example.smooth_follow]
name = "Smooth Follow"
description = "Demonstrates how to make an entity smoothly follow another using interpolation"
category = "Math"
wasm = true
# Gizmos
[[example]]
name = "2d_gizmos"
@ -3097,6 +3134,28 @@ path = "examples/dev_tools/fps_overlay.rs"
doc-scrape-examples = true
required-features = ["bevy_dev_tools"]
[[example]]
name = "2d_top_down_camera"
path = "examples/camera/2d_top_down_camera.rs"
doc-scrape-examples = true
[package.metadata.example.2d_top_down_camera]
name = "2D top-down camera"
description = "A 2D top-down camera smoothly following player movements"
category = "Camera"
wasm = true
[[example]]
name = "first_person_view_model"
path = "examples/camera/first_person_view_model.rs"
doc-scrape-examples = true
[package.metadata.example.first_person_view_model]
name = "First person view model"
description = "A first-person camera that uses a world model and a view model with different field of views (FOV)"
category = "Camera"
wasm = true
[package.metadata.example.fps_overlay]
name = "FPS overlay"
description = "Demonstrates FPS overlay"

View file

@ -49,7 +49,6 @@ fn fragment(
double_sided,
is_front,
Nt,
view.mip_bias,
);
#endif

View file

@ -14,7 +14,7 @@ bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
accesskit = "0.14"
accesskit = "0.15"
[lints]
workspace = true

View file

@ -107,7 +107,7 @@ pub struct VariableCurve {
///
/// - for `Interpolation::Step` and `Interpolation::Linear`, each keyframe is a single value
/// - for `Interpolation::CubicSpline`, each keyframe is made of three values for `tangent_in`,
/// `keyframe_value` and `tangent_out`
/// `keyframe_value` and `tangent_out`
pub keyframes: Keyframes,
/// Interpolation method to use between keyframes.
pub interpolation: Interpolation,

View file

@ -13,7 +13,6 @@ trace = []
bevy_debug_stepping = []
default = ["bevy_reflect"]
bevy_reflect = ["dep:bevy_reflect", "bevy_ecs/bevy_reflect"]
serialize = ["bevy_ecs/serde"]
[dependencies]
# bevy
@ -24,7 +23,6 @@ bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
# other
serde = { version = "1.0", features = ["derive"], optional = true }
downcast-rs = "1.2.0"
thiserror = "1.0"

View file

@ -8,7 +8,7 @@ use bevy_ecs::{
intern::Interned,
prelude::*,
schedule::{ScheduleBuildSettings, ScheduleLabel},
system::SystemId,
system::{IntoObserverSystem, SystemId},
};
#[cfg(feature = "trace")]
use bevy_utils::tracing::info_span;
@ -424,9 +424,12 @@ impl App {
self
}
/// Inserts the [`!Send`](Send) resource into the app, initialized with its default value,
/// if there is no existing instance of `R`.
pub fn init_non_send_resource<R: 'static + Default>(&mut self) -> &mut Self {
/// Inserts the [`!Send`](Send) resource into the app if there is no existing instance of `R`.
///
/// `R` must implement [`FromWorld`].
/// If `R` implements [`Default`], [`FromWorld`] will be automatically implemented and
/// initialize the [`Resource`] with [`Default::default`].
pub fn init_non_send_resource<R: 'static + FromWorld>(&mut self) -> &mut Self {
self.world_mut().init_non_send_resource::<R>();
self
}
@ -436,12 +439,7 @@ impl App {
plugin: Box<dyn Plugin>,
) -> Result<&mut Self, AppError> {
debug!("added plugin: {}", plugin.name());
if plugin.is_unique()
&& !self
.main_mut()
.plugin_names
.insert(plugin.name().to_string())
{
if plugin.is_unique() && self.main_mut().plugin_names.contains(plugin.name()) {
Err(AppError::DuplicatePlugin {
plugin_name: plugin.name().to_string(),
})?;
@ -456,6 +454,9 @@ impl App {
self.main_mut().plugin_build_depth += 1;
let result = catch_unwind(AssertUnwindSafe(|| plugin.build(self)));
self.main_mut()
.plugin_names
.insert(plugin.name().to_string());
self.main_mut().plugin_build_depth -= 1;
if let Err(payload) = result {
@ -828,6 +829,15 @@ impl App {
None
}
/// Spawns an [`Observer`] entity, which will watch for and respond to the given event.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.world_mut().observe(observer);
self
}
}
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
@ -916,11 +926,21 @@ impl Termination for AppExit {
#[cfg(test)]
mod tests {
use std::{marker::PhantomData, mem};
use std::{iter, marker::PhantomData, mem, sync::Mutex};
use bevy_ecs::{event::EventWriter, schedule::ScheduleLabel, system::Commands};
use bevy_ecs::{
change_detection::{DetectChanges, ResMut},
component::Component,
entity::Entity,
event::{Event, EventWriter, Events},
query::With,
removal_detection::RemovedComponents,
schedule::{IntoSystemConfigs, ScheduleLabel},
system::{Commands, Query, Resource},
world::{FromWorld, World},
};
use crate::{App, AppExit, Plugin, Update};
use crate::{App, AppExit, Plugin, SubApp, Update};
struct PluginA;
impl Plugin for PluginA {
@ -1123,6 +1143,62 @@ mod tests {
);
}
#[test]
fn test_update_clears_trackers_once() {
#[derive(Component, Copy, Clone)]
struct Foo;
let mut app = App::new();
app.world_mut().spawn_batch(iter::repeat(Foo).take(5));
fn despawn_one_foo(mut commands: Commands, foos: Query<Entity, With<Foo>>) {
if let Some(e) = foos.iter().next() {
commands.entity(e).despawn();
};
}
fn check_despawns(mut removed_foos: RemovedComponents<Foo>) {
let mut despawn_count = 0;
for _ in removed_foos.read() {
despawn_count += 1;
}
assert_eq!(despawn_count, 2);
}
app.add_systems(Update, despawn_one_foo);
app.update(); // Frame 0
app.update(); // Frame 1
app.add_systems(Update, check_despawns.after(despawn_one_foo));
app.update(); // Should see despawns from frames 1 & 2, but not frame 0
}
#[test]
fn test_extract_sees_changes() {
use super::AppLabel;
use crate::{self as bevy_app};
#[derive(AppLabel, Clone, Copy, Hash, PartialEq, Eq, Debug)]
struct MySubApp;
#[derive(Resource)]
struct Foo(usize);
let mut app = App::new();
app.world_mut().insert_resource(Foo(0));
app.add_systems(Update, |mut foo: ResMut<Foo>| {
foo.0 += 1;
});
let mut sub_app = SubApp::new();
sub_app.set_extract(|main_world, _sub_world| {
assert!(main_world.get_resource_ref::<Foo>().unwrap().is_changed());
});
app.insert_sub_app(MySubApp, sub_app);
app.update();
}
#[test]
fn runner_returns_correct_exit_code() {
fn raise_exits(mut exits: EventWriter<AppExit>) {
@ -1178,4 +1254,83 @@ mod tests {
// it's nice they're so small let's keep it that way.
assert_eq!(mem::size_of::<AppExit>(), mem::size_of::<u8>());
}
#[test]
fn initializing_resources_from_world() {
#[derive(Resource)]
struct TestResource;
impl FromWorld for TestResource {
fn from_world(_world: &mut World) -> Self {
TestResource
}
}
#[derive(Resource)]
struct NonSendTestResource {
_marker: PhantomData<Mutex<()>>,
}
impl FromWorld for NonSendTestResource {
fn from_world(_world: &mut World) -> Self {
NonSendTestResource {
_marker: PhantomData,
}
}
}
App::new()
.init_non_send_resource::<NonSendTestResource>()
.init_resource::<TestResource>();
}
#[test]
/// Plugin should not be considered inserted while it's being built
///
/// bug: <https://github.com/bevyengine/bevy/issues/13815>
fn plugin_should_not_be_added_during_build_time() {
pub struct Foo;
impl Plugin for Foo {
fn build(&self, app: &mut App) {
assert!(!app.is_plugin_added::<Self>());
}
}
App::new().add_plugins(Foo);
}
#[test]
fn events_should_be_updated_once_per_update() {
#[derive(Event, Clone)]
struct TestEvent;
let mut app = App::new();
app.add_event::<TestEvent>();
// Starts empty
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 0);
assert_eq!(test_events.iter_current_update_events().count(), 0);
app.update();
// Sending one event
app.world_mut().send_event(TestEvent);
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 1);
assert_eq!(test_events.iter_current_update_events().count(), 1);
app.update();
// Sending two events on the next frame
app.world_mut().send_event(TestEvent);
app.world_mut().send_event(TestEvent);
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
assert_eq!(test_events.iter_current_update_events().count(), 2);
app.update();
// Sending zero events
let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
assert_eq!(test_events.iter_current_update_events().count(), 0);
}
}

View file

@ -17,7 +17,7 @@ use bevy_ecs::{
/// Then it will run:
/// * [`First`]
/// * [`PreUpdate`]
/// * [`StateTransition`]
/// * [`StateTransition`](bevy_state::transition::StateTransition)
/// * [`RunFixedMainLoop`]
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
/// * [`Update`]

View file

@ -12,7 +12,7 @@ use crate::Plugin;
/// Adds sensible panic handlers to Apps. This plugin is part of the `DefaultPlugins`. Adding
/// this plugin will setup a panic hook appropriate to your target platform:
/// * On WASM, uses [`console_error_panic_hook`](https://crates.io/crates/console_error_panic_hook), logging
/// to the browser console.
/// to the browser console.
/// * Other platforms are currently not setup.
///
/// ```no_run

View file

@ -125,7 +125,9 @@ impl SubApp {
}
/// Runs the default schedule.
pub fn update(&mut self) {
///
/// Does not clear internal trackers used for change detection.
pub fn run_default_schedule(&mut self) {
if self.is_building_plugins() {
panic!("SubApp::update() was called while a plugin was building.");
}
@ -133,6 +135,11 @@ impl SubApp {
if let Some(label) = self.update_schedule {
self.world.run_schedule(label);
}
}
/// Runs the default schedule and updates internal component trackers.
pub fn update(&mut self) {
self.run_default_schedule();
self.world.clear_trackers();
}
@ -421,7 +428,7 @@ impl SubApps {
{
#[cfg(feature = "trace")]
let _bevy_frame_update_span = info_span!("main app").entered();
self.main.update();
self.main.run_default_schedule();
}
for (_label, sub_app) in self.sub_apps.iter_mut() {
#[cfg(feature = "trace")]

View file

@ -306,8 +306,8 @@ pub trait AssetApp {
/// * Initializing the [`AssetEvent`] resource for the [`Asset`]
/// * Adding other relevant systems and resources for the [`Asset`]
/// * Ignoring schedule ambiguities in [`Assets`] resource. Any time a system takes
/// mutable access to this resource this causes a conflict, but they rarely actually
/// modify the same underlying asset.
/// mutable access to this resource this causes a conflict, but they rarely actually
/// modify the same underlying asset.
fn init_asset<A: Asset>(&mut self) -> &mut Self;
/// Registers the asset type `T` using `[App::register]`,
/// and adds [`ReflectAsset`] type data to `T` and [`ReflectHandle`] type data to [`Handle<T>`] in the type registry.
@ -1511,6 +1511,7 @@ mod tests {
Empty,
}
#[allow(dead_code)]
#[derive(Asset, TypePath)]
pub struct StructTestAsset {
#[dependency]
@ -1519,6 +1520,7 @@ mod tests {
embedded: TestAsset,
}
#[allow(dead_code)]
#[derive(Asset, TypePath)]
pub struct TupleTestAsset(#[dependency] Handle<TestAsset>);
}

View file

@ -17,7 +17,7 @@ use thiserror::Error;
/// This is optional. If one is not set the default source will be used (which is the `assets` folder by default).
/// * [`AssetPath::path`]: The "virtual filesystem path" pointing to an asset source file.
/// * [`AssetPath::label`]: An optional "named sub asset". When assets are loaded, they are
/// allowed to load "sub assets" of any type, which are identified by a named "label".
/// allowed to load "sub assets" of any type, which are identified by a named "label".
///
/// Asset paths are generally constructed (and visualized) as strings:
///

View file

@ -170,7 +170,7 @@ impl AssetProcessor {
/// * Scan the processed [`AssetReader`] to build the current view of already processed assets.
/// * Scan the unprocessed [`AssetReader`] and remove any final processed assets that are invalid or no longer exist.
/// * For each asset in the unprocessed [`AssetReader`], kick off a new "process job", which will process the asset
/// (if the latest version of the asset has not been processed).
/// (if the latest version of the asset has not been processed).
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
pub fn process_assets(&self) {
let start_time = std::time::Instant::now();
@ -1098,6 +1098,7 @@ pub(crate) struct ProcessorAssetInfo {
/// * when the processor is running in parallel with an app
/// * when processing assets in parallel, the processor might read an asset's `process_dependencies` when processing new versions of those dependencies
/// * this second scenario almost certainly isn't possible with the current implementation, but its worth protecting against
///
/// This lock defends against those scenarios by ensuring readers don't read while processed files are being written. And it ensures
/// Because this lock is shared across meta and asset bytes, readers can ensure they don't read "old" versions of metadata with "new" asset data.
pub(crate) file_transaction_lock: Arc<async_lock::RwLock<()>>,

View file

@ -40,7 +40,7 @@ use crate::io::{AssetReader, AssetWriter};
///
/// The general process to load an asset is:
/// 1. Initialize a new [`Asset`] type with the [`AssetServer`] via [`AssetApp::init_asset`], which will internally call [`AssetServer::register_asset`]
/// and set up related ECS [`Assets`] storage and systems.
/// and set up related ECS [`Assets`] storage and systems.
/// 2. Register one or more [`AssetLoader`]s for that asset with [`AssetApp::init_asset_loader`]
/// 3. Add the asset to your asset folder (defaults to `assets`).
/// 4. Call [`AssetServer::load`] with a path to your asset.
@ -718,9 +718,9 @@ impl AssetServer {
///
/// After the asset has been fully loaded, it will show up in the relevant [`Assets`] storage.
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
pub fn add_async<A: Asset>(
pub fn add_async<A: Asset, E: std::error::Error + Send + Sync + 'static>(
&self,
future: impl Future<Output = Result<A, AssetLoadError>> + Send + 'static,
future: impl Future<Output = Result<A, E>> + Send + 'static,
) -> Handle<A> {
let handle = self
.data
@ -741,12 +741,15 @@ impl AssetServer {
.unwrap();
}
Err(error) => {
let error = AddAsyncError {
error: Arc::new(error),
};
error!("{error}");
event_sender
.send(InternalAssetEvent::Failed {
id,
path: Default::default(),
error,
error: AssetLoadError::AddAsyncError(error),
})
.unwrap();
}
@ -1403,6 +1406,8 @@ pub enum AssetLoadError {
CannotLoadIgnoredAsset { path: AssetPath<'static> },
#[error(transparent)]
AssetLoaderError(#[from] AssetLoaderError),
#[error(transparent)]
AddAsyncError(#[from] AddAsyncError),
#[error("The file at '{}' does not contain the labeled asset '{}'; it contains the following {} assets: {}",
base_path,
label,
@ -1441,6 +1446,22 @@ impl AssetLoaderError {
}
}
#[derive(Error, Debug, Clone)]
#[error("An error occurred while resolving an asset added by `add_async`: {error}")]
pub struct AddAsyncError {
error: Arc<dyn std::error::Error + Send + Sync + 'static>,
}
impl PartialEq for AddAsyncError {
/// Equality comparison is not full (only through `TypeId`)
#[inline]
fn eq(&self, other: &Self) -> bool {
self.error.type_id() == other.error.type_id()
}
}
impl Eq for AddAsyncError {}
/// An error that occurs when an [`AssetLoader`] is not registered for a given extension.
#[derive(Error, Debug, Clone, PartialEq, Eq)]
#[error("no `AssetLoader` found{}", format_missing_asset_ext(.extensions))]

View file

@ -10,17 +10,18 @@ keywords = ["bevy", "color"]
rust-version = "1.76.0"
[dependencies]
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.14.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
"bevy",
] }
], optional = true }
bytemuck = { version = "1", features = ["derive"] }
serde = { version = "1.0", features = ["derive"], optional = true }
thiserror = "1.0"
wgpu-types = { version = "0.19", default-features = false, optional = true }
wgpu-types = { version = "0.20", default-features = false, optional = true }
encase = { version = "0.8", default-features = false }
[features]
default = ["bevy_reflect"]
serialize = ["serde"]
[lints]

View file

@ -2,6 +2,7 @@ use crate::{
color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba,
Luminance, Mix, Oklaba, Oklcha, Srgba, StandardColor, Xyza,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// An enumerated type that can represent any of the color types in this crate.
@ -39,11 +40,11 @@ use bevy_reflect::prelude::*;
/// due to its perceptual uniformity and broad support for Bevy's color operations.
/// To avoid the cost of repeated conversion, and ensure consistent results where that is desired,
/// first convert this [`Color`] into your desired color space.
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub enum Color {
@ -73,7 +74,12 @@ impl StandardColor for Color {}
impl Color {
/// Return the color as a linear RGBA color.
pub fn linear(&self) -> LinearRgba {
pub fn to_linear(&self) -> LinearRgba {
(*self).into()
}
/// Return the color as an SRGBA color.
pub fn to_srgba(&self) -> Srgba {
(*self).into()
}

View file

@ -115,6 +115,18 @@ pub trait ColorToComponents {
fn from_vec3(color: Vec3) -> Self;
}
/// Trait with methods for converting colors to packed non-color types
pub trait ColorToPacked {
/// Convert to [u8; 4] where that makes sense (Srgba is most relevant)
fn to_u8_array(self) -> [u8; 4];
/// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
fn to_u8_array_no_alpha(self) -> [u8; 3];
/// Convert from [u8; 4] where that makes sense (Srgba is most relevant)
fn from_u8_array(color: [u8; 4]) -> Self;
/// Convert to [u8; 3] where that makes sense (Srgba is most relevant)
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self;
}
/// Utility function for interpolating hue values. This ensures that the interpolation
/// takes the shortest path around the color wheel, and that the result is always between
/// 0 and 360.

View file

@ -3,6 +3,7 @@ use crate::{
StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in Hue-Saturation-Lightness (HSL) color space with alpha.
@ -11,11 +12,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Hsla {

View file

@ -2,6 +2,7 @@ use crate::{
Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Srgba, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in Hue-Saturation-Value (HSV) color space with alpha.
@ -10,11 +11,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Hsva {

View file

@ -6,6 +6,7 @@ use crate::{
Alpha, ColorToComponents, Gray, Hue, Lcha, LinearRgba, Mix, Srgba, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in Hue-Whiteness-Blackness (HWB) color space with alpha.
@ -14,11 +15,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Hwba {

View file

@ -3,6 +3,7 @@ use crate::{
Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in LAB color space, with alpha
@ -10,11 +11,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Laba {

View file

@ -3,6 +3,7 @@ use crate::{
Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in LCH color space, with alpha
@ -10,11 +11,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Lcha {

View file

@ -147,7 +147,6 @@ where
Self: core::fmt::Debug,
Self: Clone + Copy,
Self: PartialEq,
Self: bevy_reflect::Reflect,
Self: Default,
Self: From<Color> + Into<Color>,
Self: From<Srgba> + Into<Srgba>,

View file

@ -1,8 +1,9 @@
use crate::{
color_difference::EuclideanDistance, impl_componentwise_vector_space, Alpha, ColorToComponents,
Gray, Luminance, Mix, StandardColor,
ColorToPacked, Gray, Luminance, Mix, StandardColor,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
use bytemuck::{Pod, Zeroable};
@ -11,11 +12,11 @@ use bytemuck::{Pod, Zeroable};
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect, Pod, Zeroable)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
#[repr(C)]
@ -149,24 +150,12 @@ impl LinearRgba {
}
}
/// Converts the color into a [f32; 4] array in RGBA order.
///
/// This is useful for passing the color to a shader.
pub fn to_f32_array(&self) -> [f32; 4] {
[self.red, self.green, self.blue, self.alpha]
}
/// Converts this color to a u32.
///
/// Maps the RGBA channels in RGBA order to a little-endian byte array (GPUs are little-endian).
/// `A` will be the most significant byte and `R` the least significant.
pub fn as_u32(&self) -> u32 {
u32::from_le_bytes([
(self.red * 255.0) as u8,
(self.green * 255.0) as u8,
(self.blue * 255.0) as u8,
(self.alpha * 255.0) as u8,
])
u32::from_le_bytes(self.to_u8_array())
}
}
@ -310,6 +299,25 @@ impl ColorToComponents for LinearRgba {
}
}
impl ColorToPacked for LinearRgba {
fn to_u8_array(self) -> [u8; 4] {
[self.red, self.green, self.blue, self.alpha]
.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn to_u8_array_no_alpha(self) -> [u8; 3] {
[self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn from_u8_array(color: [u8; 4]) -> Self {
Self::from_f32_array(color.map(|u| u as f32 / 255.0))
}
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
}
}
#[cfg(feature = "wgpu-types")]
impl From<LinearRgba> for wgpu_types::Color {
fn from(color: LinearRgba) -> Self {
@ -416,6 +424,34 @@ mod tests {
assert_eq!(a.distance_squared(&b), 1.0);
}
#[test]
fn to_and_from_u8() {
// from_u8_array
let a = LinearRgba::from_u8_array([255, 0, 0, 255]);
let b = LinearRgba::new(1.0, 0.0, 0.0, 1.0);
assert_eq!(a, b);
// from_u8_array_no_alpha
let a = LinearRgba::from_u8_array_no_alpha([255, 255, 0]);
let b = LinearRgba::rgb(1.0, 1.0, 0.0);
assert_eq!(a, b);
// to_u8_array
let a = LinearRgba::new(0.0, 0.0, 1.0, 1.0).to_u8_array();
let b = [0, 0, 255, 255];
assert_eq!(a, b);
// to_u8_array_no_alpha
let a = LinearRgba::rgb(0.0, 1.0, 1.0).to_u8_array_no_alpha();
let b = [0, 255, 255];
assert_eq!(a, b);
// clamping
let a = LinearRgba::rgb(0.0, 100.0, -100.0).to_u8_array_no_alpha();
let b = [0, 255, 0];
assert_eq!(a, b);
}
#[test]
fn darker_lighter() {
// Darker and lighter should be commutative.

View file

@ -3,6 +3,7 @@ use crate::{
Gray, Hsla, Hsva, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in Oklab color space, with alpha
@ -10,11 +11,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Oklaba {

View file

@ -3,6 +3,7 @@ use crate::{
Laba, Lcha, LinearRgba, Luminance, Mix, Oklaba, Srgba, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// Color in Oklch color space, with alpha
@ -10,11 +11,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Oklcha {

View file

@ -1,9 +1,10 @@
use crate::color_difference::EuclideanDistance;
use crate::{
impl_componentwise_vector_space, Alpha, ColorToComponents, Gray, LinearRgba, Luminance, Mix,
StandardColor, Xyza,
impl_componentwise_vector_space, Alpha, ColorToComponents, ColorToPacked, Gray, LinearRgba,
Luminance, Mix, StandardColor, Xyza,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
use thiserror::Error;
@ -12,11 +13,11 @@ use thiserror::Error;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Srgba {
@ -168,10 +169,7 @@ impl Srgba {
/// Convert this color to CSS-style hexadecimal notation.
pub fn to_hex(&self) -> String {
let r = (self.red * 255.0).round() as u8;
let g = (self.green * 255.0).round() as u8;
let b = (self.blue * 255.0).round() as u8;
let a = (self.alpha * 255.0).round() as u8;
let [r, g, b, a] = self.to_u8_array();
match a {
255 => format!("#{:02X}{:02X}{:02X}", r, g, b),
_ => format!("#{:02X}{:02X}{:02X}{:02X}", r, g, b, a),
@ -189,7 +187,7 @@ impl Srgba {
/// See also [`Srgba::new`], [`Srgba::rgba_u8`], [`Srgba::hex`].
///
pub fn rgb_u8(r: u8, g: u8, b: u8) -> Self {
Self::rgba_u8(r, g, b, u8::MAX)
Self::from_u8_array_no_alpha([r, g, b])
}
// Float operations in const fn are not stable yet
@ -206,12 +204,7 @@ impl Srgba {
/// See also [`Srgba::new`], [`Srgba::rgb_u8`], [`Srgba::hex`].
///
pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Self {
Self::new(
r as f32 / u8::MAX as f32,
g as f32 / u8::MAX as f32,
b as f32 / u8::MAX as f32,
a as f32 / u8::MAX as f32,
)
Self::from_u8_array([r, g, b, a])
}
/// Converts a non-linear sRGB value to a linear one via [gamma correction](https://en.wikipedia.org/wiki/Gamma_correction).
@ -373,6 +366,25 @@ impl ColorToComponents for Srgba {
}
}
impl ColorToPacked for Srgba {
fn to_u8_array(self) -> [u8; 4] {
[self.red, self.green, self.blue, self.alpha]
.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn to_u8_array_no_alpha(self) -> [u8; 3] {
[self.red, self.green, self.blue].map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8)
}
fn from_u8_array(color: [u8; 4]) -> Self {
Self::from_f32_array(color.map(|u| u as f32 / 255.0))
}
fn from_u8_array_no_alpha(color: [u8; 3]) -> Self {
Self::from_f32_array_no_alpha(color.map(|u| u as f32 / 255.0))
}
}
impl From<LinearRgba> for Srgba {
#[inline]
fn from(value: LinearRgba) -> Self {

View file

@ -3,6 +3,7 @@ use crate::{
StandardColor,
};
use bevy_math::{Vec3, Vec4};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::prelude::*;
/// [CIE 1931](https://en.wikipedia.org/wiki/CIE_1931_color_space) color space, also known as XYZ, with an alpha channel.
@ -10,11 +11,11 @@ use bevy_reflect::prelude::*;
/// <div>
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
/// </div>
#[derive(Debug, Clone, Copy, PartialEq, Reflect)]
#[reflect(PartialEq, Default)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
all(feature = "serialize", feature = "bevy_reflect"),
reflect(Serialize, Deserialize)
)]
pub struct Xyza {

View file

@ -32,6 +32,7 @@ pub struct AutoExposureCompensationCurve {
/// Each value in the LUT is a `u8` representing a normalized exposure compensation value:
/// * `0` maps to `min_compensation`
/// * `255` maps to `max_compensation`
///
/// The position in the LUT corresponds to the normalized log luminance value.
/// * `0` maps to `min_log_lum`
/// * `LUT_SIZE - 1` maps to `max_log_lum`

View file

@ -67,6 +67,7 @@ pub struct AutoExposureSettings {
/// The mask to apply when metering. The mask will cover the entire screen, where:
/// * `(0.0, 0.0)` is the top-left corner,
/// * `(1.0, 1.0)` is the bottom-right corner.
///
/// Only the red channel of the texture is used.
/// The sample at the current screen position will be used to weight the contribution
/// of each pixel to the histogram:

View file

@ -30,7 +30,7 @@ pub mod states;
/// To enable developer tools, you can either:
///
/// - Create a custom crate feature (e.g "`dev_mode`"), which enables the `bevy_dev_tools` feature
/// along with any other development tools you might be using:
/// along with any other development tools you might be using:
///
/// ```toml
/// [feature]

View file

@ -20,6 +20,7 @@ bevy_core = { path = "../bevy_core", version = "0.14.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.14.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.14.0-dev" }
const-fnv1a-hash = "1.1.0"

View file

@ -4,6 +4,9 @@ use bevy_ecs::system::Resource;
/// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %)
///
/// Note that gathering system information is a time intensive task and therefore can't be done on every frame.
/// Any system diagnostics gathered by this plugin may not be current when you access them.
///
/// Supported targets:
/// * linux,
/// * windows,
@ -19,8 +22,7 @@ use bevy_ecs::system::Resource;
pub struct SystemInformationDiagnosticsPlugin;
impl Plugin for SystemInformationDiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, internal::setup_system)
.add_systems(Update, internal::diagnostic_system);
internal::setup_plugin(app);
}
}
@ -58,6 +60,14 @@ pub struct SystemInfo {
))]
pub mod internal {
use bevy_ecs::{prelude::ResMut, system::Local};
use std::{
sync::{Arc, Mutex},
time::Instant,
};
use bevy_app::{App, First, Startup, Update};
use bevy_ecs::system::Resource;
use bevy_tasks::{available_parallelism, block_on, poll_once, AsyncComputeTaskPool, Task};
use bevy_utils::tracing::info;
use sysinfo::{CpuRefreshKind, MemoryRefreshKind, RefreshKind, System};
@ -67,41 +77,91 @@ pub mod internal {
const BYTES_TO_GIB: f64 = 1.0 / 1024.0 / 1024.0 / 1024.0;
pub(crate) fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
pub(super) fn setup_plugin(app: &mut App) {
app.add_systems(Startup, setup_system)
.add_systems(First, launch_diagnostic_tasks)
.add_systems(Update, read_diagnostic_tasks)
.init_resource::<SysinfoTasks>();
}
fn setup_system(mut diagnostics: ResMut<DiagnosticsStore>) {
diagnostics
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::CPU_USAGE).with_suffix("%"));
diagnostics
.add(Diagnostic::new(SystemInformationDiagnosticsPlugin::MEM_USAGE).with_suffix("%"));
}
pub(crate) fn diagnostic_system(
mut diagnostics: Diagnostics,
mut sysinfo: Local<Option<System>>,
struct SysinfoRefreshData {
current_cpu_usage: f64,
current_used_mem: f64,
}
#[derive(Resource, Default)]
struct SysinfoTasks {
tasks: Vec<Task<SysinfoRefreshData>>,
}
fn launch_diagnostic_tasks(
mut tasks: ResMut<SysinfoTasks>,
// TODO: Consider a fair mutex
mut sysinfo: Local<Option<Arc<Mutex<System>>>>,
// TODO: FromWorld for Instant?
mut last_refresh: Local<Option<Instant>>,
) {
if sysinfo.is_none() {
*sysinfo = Some(System::new_with_specifics(
let sysinfo = sysinfo.get_or_insert_with(|| {
Arc::new(Mutex::new(System::new_with_specifics(
RefreshKind::new()
.with_cpu(CpuRefreshKind::new().with_cpu_usage())
.with_memory(MemoryRefreshKind::everything()),
));
}
let Some(sys) = sysinfo.as_mut() else {
return;
};
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
sys.refresh_memory();
let current_cpu_usage = sys.global_cpu_info().cpu_usage();
// `memory()` fns return a value in bytes
let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB;
let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB;
let current_used_mem = used_mem / total_mem * 100.0;
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || {
current_cpu_usage as f64
)))
});
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
current_used_mem
let last_refresh = last_refresh.get_or_insert_with(Instant::now);
let thread_pool = AsyncComputeTaskPool::get();
// Only queue a new system refresh task when necessary
// Queueing earlier than that will not give new data
if last_refresh.elapsed() > sysinfo::MINIMUM_CPU_UPDATE_INTERVAL
// These tasks don't yield and will take up all of the task pool's
// threads if we don't limit their amount.
&& tasks.tasks.len() * 2 < available_parallelism()
{
let sys = Arc::clone(sysinfo);
let task = thread_pool.spawn(async move {
let mut sys = sys.lock().unwrap();
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
sys.refresh_memory();
let current_cpu_usage = sys.global_cpu_info().cpu_usage().into();
// `memory()` fns return a value in bytes
let total_mem = sys.total_memory() as f64 / BYTES_TO_GIB;
let used_mem = sys.used_memory() as f64 / BYTES_TO_GIB;
let current_used_mem = used_mem / total_mem * 100.0;
SysinfoRefreshData {
current_cpu_usage,
current_used_mem,
}
});
tasks.tasks.push(task);
*last_refresh = Instant::now();
}
}
fn read_diagnostic_tasks(mut diagnostics: Diagnostics, mut tasks: ResMut<SysinfoTasks>) {
tasks.tasks.retain_mut(|task| {
let Some(data) = block_on(poll_once(task)) else {
return true;
};
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::CPU_USAGE, || {
data.current_cpu_usage
});
diagnostics.add_measurement(&SystemInformationDiagnosticsPlugin::MEM_USAGE, || {
data.current_used_mem
});
false
});
}
@ -145,12 +205,14 @@ pub mod internal {
not(feature = "dynamic_linking")
)))]
pub mod internal {
pub(crate) fn setup_system() {
bevy_utils::tracing::warn!("This platform and/or configuration is not supported!");
use bevy_app::{App, Startup};
pub(super) fn setup_plugin(app: &mut App) {
app.add_systems(Startup, setup_system);
}
pub(crate) fn diagnostic_system() {
// no-op
fn setup_system() {
bevy_utils::tracing::warn!("This platform and/or configuration is not supported!");
}
impl Default for super::SystemInfo {

View file

@ -15,6 +15,7 @@ trace = []
multi_threaded = ["bevy_tasks/multi_threaded", "arrayvec"]
bevy_debug_stepping = []
default = ["bevy_reflect"]
serialize = ["dep:serde"]
[dependencies]
bevy_ptr = { path = "../bevy_ptr", version = "0.14.0-dev" }

View file

@ -307,4 +307,52 @@ fn reader(mut reader: EventReader<MyEvent>) {
A minimal set up using events can be seen in [`events.rs`](examples/events.rs).
### Observers
Observers are systems that listen for a "trigger" of a specific `Event`:
```rust
use bevy_ecs::prelude::*;
#[derive(Event)]
struct MyEvent {
message: String
}
let mut world = World::new();
world.observe(|trigger: Trigger<MyEvent>| {
println!("{}", trigger.event().message);
});
world.flush();
world.trigger(MyEvent {
message: "hello!".to_string(),
});
```
These differ from `EventReader` and `EventWriter` in that they are "reactive". Rather than happening at a specific point in a schedule, they happen _immediately_ whenever a trigger happens. Triggers can trigger other triggers, and they all will be evaluated at the same time!
Events can also be triggered to target specific entities:
```rust
use bevy_ecs::prelude::*;
#[derive(Event)]
struct Explode;
let mut world = World::new();
let entity = world.spawn_empty().id();
world.observe(|trigger: Trigger<Explode>, mut commands: Commands| {
println!("Entity {:?} goes BOOM!", trigger.entity());
commands.entity(trigger.entity()).despawn();
});
world.flush();
world.trigger_targets(Explode, entity);
```
[bevy]: https://bevyengine.org/

View file

@ -18,6 +18,10 @@ pub fn derive_event(input: TokenStream) -> TokenStream {
TokenStream::from(quote! {
impl #impl_generics #bevy_ecs_path::event::Event for #struct_name #type_generics #where_clause {
}
impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause {
const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #bevy_ecs_path::component::StorageType::SparseSet;
}
})
}

View file

@ -74,6 +74,7 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
.collect::<Vec<_>>();
let mut field_component_ids = Vec::new();
let mut field_get_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
for (((i, field_type), field_kind), field) in field_type
@ -87,6 +88,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::component_ids(components, storages, &mut *ids);
});
field_get_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::Bundle>::get_component_ids(components, &mut *ids);
});
match field {
Some(field) => {
field_get_components.push(quote! {
@ -133,6 +137,13 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
#(#field_component_ids)*
}
fn get_component_ids(
components: &#ecs_path::component::Components,
ids: &mut impl FnMut(Option<#ecs_path::component::ComponentId>)
){
#(#field_get_component_ids)*
}
#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
@ -435,6 +446,10 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::apply(&mut state.state, system_meta, world);
}
fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: #path::world::DeferredWorld) {
<#fields_alias::<'_, '_, #punctuated_generic_idents> as #path::system::SystemParam>::queue(&mut state.state, system_meta, world);
}
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &#path::system::SystemMeta,

View file

@ -164,9 +164,9 @@ pub(crate) fn world_query_impl(
#( <#field_types>::update_component_access(&state.#named_field_idents, _access); )*
}
fn init_state(initializer: &mut #path::component::ComponentInitializer) -> #state_struct_name #user_ty_generics {
fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics {
#state_struct_name {
#(#named_field_idents: <#field_types>::init_state(initializer),)*
#(#named_field_idents: <#field_types>::init_state(world),)*
}
}

View file

@ -23,6 +23,7 @@ use crate::{
bundle::BundleId,
component::{ComponentId, Components, StorageType},
entity::{Entity, EntityLocation},
observer::Observers,
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
};
use std::{
@ -119,6 +120,7 @@ pub(crate) struct AddBundle {
/// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle),
/// indicate if the component is newly added to the target archetype or if it already existed
pub bundle_status: Vec<ComponentStatus>,
pub added: Vec<ComponentId>,
}
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
@ -202,12 +204,14 @@ impl Edges {
bundle_id: BundleId,
archetype_id: ArchetypeId,
bundle_status: Vec<ComponentStatus>,
added: Vec<ComponentId>,
) {
self.add_bundle.insert(
bundle_id,
AddBundle {
archetype_id,
bundle_status,
added,
},
);
}
@ -314,6 +318,9 @@ bitflags::bitflags! {
const ON_ADD_HOOK = (1 << 0);
const ON_INSERT_HOOK = (1 << 1);
const ON_REMOVE_HOOK = (1 << 2);
const ON_ADD_OBSERVER = (1 << 3);
const ON_INSERT_OBSERVER = (1 << 4);
const ON_REMOVE_OBSERVER = (1 << 5);
}
}
@ -335,6 +342,7 @@ pub struct Archetype {
impl Archetype {
pub(crate) fn new(
components: &Components,
observers: &Observers,
id: ArchetypeId,
table_id: TableId,
table_components: impl Iterator<Item = (ComponentId, ArchetypeComponentId)>,
@ -348,6 +356,7 @@ impl Archetype {
// SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags);
observers.update_archetype_flags(component_id, &mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
@ -361,6 +370,7 @@ impl Archetype {
// SAFETY: We are creating an archetype that includes this component so it must exist
let info = unsafe { components.get_info_unchecked(component_id) };
info.update_archetype_flags(&mut flags);
observers.update_archetype_flags(component_id, &mut flags);
archetype_components.insert(
component_id,
ArchetypeComponentInfo {
@ -580,21 +590,45 @@ impl Archetype {
/// Returns true if any of the components in this archetype have `on_add` hooks
#[inline]
pub(crate) fn has_on_add(&self) -> bool {
pub fn has_add_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_HOOK)
}
/// Returns true if any of the components in this archetype have `on_insert` hooks
#[inline]
pub(crate) fn has_on_insert(&self) -> bool {
pub fn has_insert_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_HOOK)
}
/// Returns true if any of the components in this archetype have `on_remove` hooks
#[inline]
pub(crate) fn has_on_remove(&self) -> bool {
pub fn has_remove_hook(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_HOOK)
}
/// Returns true if any of the components in this archetype have at least one [`OnAdd`] observer
///
/// [`OnAdd`]: crate::world::OnAdd
#[inline]
pub fn has_add_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_ADD_OBSERVER)
}
/// Returns true if any of the components in this archetype have at least one [`OnInsert`] observer
///
/// [`OnInsert`]: crate::world::OnInsert
#[inline]
pub fn has_insert_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_INSERT_OBSERVER)
}
/// Returns true if any of the components in this archetype have at least one [`OnRemove`] observer
///
/// [`OnRemove`]: crate::world::OnRemove
#[inline]
pub fn has_remove_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
}
}
/// The next [`ArchetypeId`] in an [`Archetypes`] collection.
@ -681,6 +715,7 @@ impl Archetypes {
unsafe {
archetypes.get_id_or_insert(
&Components::default(),
&Observers::default(),
TableId::empty(),
Vec::new(),
Vec::new(),
@ -782,13 +817,14 @@ impl Archetypes {
pub(crate) unsafe fn get_id_or_insert(
&mut self,
components: &Components,
observers: &Observers,
table_id: TableId,
table_components: Vec<ComponentId>,
sparse_set_components: Vec<ComponentId>,
) -> ArchetypeId {
let archetype_identity = ArchetypeComponents {
sparse_set_components: sparse_set_components.clone().into_boxed_slice(),
table_components: table_components.clone().into_boxed_slice(),
sparse_set_components: sparse_set_components.into_boxed_slice(),
table_components: table_components.into_boxed_slice(),
};
let archetypes = &mut self.archetypes;
@ -796,23 +832,35 @@ impl Archetypes {
*self
.by_components
.entry(archetype_identity)
.or_insert_with(move || {
.or_insert_with_key(move |identity| {
let ArchetypeComponents {
table_components,
sparse_set_components,
} = identity;
let id = ArchetypeId::new(archetypes.len());
let table_start = *archetype_component_count;
*archetype_component_count += table_components.len();
let table_archetype_components =
(table_start..*archetype_component_count).map(ArchetypeComponentId);
let sparse_start = *archetype_component_count;
*archetype_component_count += sparse_set_components.len();
let sparse_set_archetype_components =
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
archetypes.push(Archetype::new(
components,
observers,
id,
table_id,
table_components.into_iter().zip(table_archetype_components),
table_components
.iter()
.copied()
.zip(table_archetype_components),
sparse_set_components
.into_iter()
.iter()
.copied()
.zip(sparse_set_archetype_components),
));
id
@ -832,6 +880,20 @@ impl Archetypes {
archetype.clear_entities();
}
}
pub(crate) fn update_flags(
&mut self,
component_id: ComponentId,
flags: ArchetypeFlags,
set: bool,
) {
// TODO: Refactor component index to speed this up.
for archetype in &mut self.archetypes {
if archetype.contains(component_id) {
archetype.flags.set(flags, set);
}
}
}
}
impl Index<RangeFrom<ArchetypeGeneration>> for Archetypes {

View file

@ -2,8 +2,9 @@
//!
//! This module contains the [`Bundle`] trait and some other helper types.
use std::any::TypeId;
pub use bevy_ecs_macros::Bundle;
use bevy_utils::{HashMap, HashSet, TypeIdMap};
use crate::{
archetype::{
@ -12,14 +13,15 @@ use crate::{
},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
observer::Observers,
prelude::World,
query::DebugCheckedUnwrap,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT},
};
use bevy_ptr::{ConstNonNull, OwningPtr};
use bevy_utils::all_tuples;
use std::any::TypeId;
use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap};
use std::ptr::NonNull;
/// The `Bundle` trait enables insertion and removal of [`Component`]s from an entity.
@ -155,6 +157,9 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static {
ids: &mut impl FnMut(ComponentId),
);
/// Gets this [`Bundle`]'s component ids. This will be [`None`] if the component has not been registered.
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>));
/// Calls `func`, which should return data for each component in the bundle, in the order of
/// this bundle's [`Component`]s
///
@ -204,6 +209,10 @@ unsafe impl<C: Component> Bundle for C {
// Safety: The id given in `component_ids` is for `Self`
unsafe { ptr.read() }
}
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)) {
ids(components.get_id(TypeId::of::<C>()));
}
}
impl<C: Component> DynamicBundle for C {
@ -227,6 +236,11 @@ macro_rules! tuple_impl {
$(<$name as Bundle>::component_ids(components, storages, ids);)*
}
#[allow(unused_variables)]
fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option<ComponentId>)){
$(<$name as Bundle>::get_component_ids(components, ids);)*
}
#[allow(unused_variables, unused_mut)]
#[allow(clippy::unused_unit)]
unsafe fn from_components<T, F>(ctx: &mut T, func: &mut F) -> Self
@ -310,7 +324,7 @@ impl BundleInfo {
id: BundleId,
) -> BundleInfo {
let mut deduped = component_ids.clone();
deduped.sort();
deduped.sort_unstable();
deduped.dedup();
if deduped.len() != component_ids.len() {
@ -432,6 +446,7 @@ impl BundleInfo {
archetypes: &mut Archetypes,
storages: &mut Storages,
components: &Components,
observers: &Observers,
archetype_id: ArchetypeId,
) -> ArchetypeId {
if let Some(add_bundle_id) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
@ -440,6 +455,7 @@ impl BundleInfo {
let mut new_table_components = Vec::new();
let mut new_sparse_set_components = Vec::new();
let mut bundle_status = Vec::with_capacity(self.component_ids.len());
let mut added = Vec::new();
let current_archetype = &mut archetypes[archetype_id];
for component_id in self.component_ids.iter().cloned() {
@ -447,6 +463,7 @@ impl BundleInfo {
bundle_status.push(ComponentStatus::Mutated);
} else {
bundle_status.push(ComponentStatus::Added);
added.push(component_id);
// SAFETY: component_id exists
let component_info = unsafe { components.get_info_unchecked(component_id) };
match component_info.storage_type() {
@ -459,7 +476,7 @@ impl BundleInfo {
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
let edges = current_archetype.edges_mut();
// the archetype does not change when we add this bundle
edges.insert_add_bundle(self.id, archetype_id, bundle_status);
edges.insert_add_bundle(self.id, archetype_id, bundle_status, added);
archetype_id
} else {
let table_id;
@ -475,7 +492,7 @@ impl BundleInfo {
} else {
new_table_components.extend(current_archetype.table_components());
// sort to ignore order while hashing
new_table_components.sort();
new_table_components.sort_unstable();
// SAFETY: all component ids in `new_table_components` exist
table_id = unsafe {
storages
@ -491,13 +508,14 @@ impl BundleInfo {
} else {
new_sparse_set_components.extend(current_archetype.sparse_set_components());
// sort to ignore order while hashing
new_sparse_set_components.sort();
new_sparse_set_components.sort_unstable();
new_sparse_set_components
};
};
// SAFETY: ids in self must be valid
let new_archetype_id = archetypes.get_id_or_insert(
components,
observers,
table_id,
table_components,
sparse_set_components,
@ -507,6 +525,7 @@ impl BundleInfo {
self.id,
new_archetype_id,
bundle_status,
added,
);
new_archetype_id
}
@ -567,6 +586,7 @@ impl<'w> BundleInserter<'w> {
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
archetype_id,
);
if new_archetype_id == archetype_id {
@ -786,27 +806,21 @@ impl<'w> BundleInserter<'w> {
}
};
let new_archetype = &*new_archetype;
// SAFETY: We have no outstanding mutable references to world as they were dropped
let mut deferred_world = unsafe { self.world.into_deferred() };
if new_archetype.has_on_add() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(
entity,
bundle_info
.iter_components()
.zip(add_bundle.bundle_status.iter())
.filter(|(_, &status)| status == ComponentStatus::Added)
.map(|(id, _)| id),
);
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(new_archetype, entity, add_bundle.added.iter().cloned());
if new_archetype.has_add_observer() {
deferred_world.trigger_observers(ON_ADD, entity, add_bundle.added.iter().cloned());
}
deferred_world.trigger_on_insert(new_archetype, entity, bundle_info.iter_components());
if new_archetype.has_insert_observer() {
deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components());
}
}
if new_archetype.has_on_insert() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) }
}
new_location
@ -853,6 +867,7 @@ impl<'w> BundleSpawner<'w> {
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
ArchetypeId::EMPTY,
);
let archetype = &mut world.archetypes[new_archetype_id];
@ -882,12 +897,12 @@ impl<'w> BundleSpawner<'w> {
entity: Entity,
bundle: T,
) -> EntityLocation {
let table = self.table.as_mut();
let archetype = self.archetype.as_mut();
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
let bundle_info = self.bundle_info.as_ref();
// SAFETY: We do not make any structural changes to the archetype graph through self.world so this pointer always remain valid
let location = {
let table = self.table.as_mut();
let archetype = self.archetype.as_mut();
// SAFETY: Mutable references do not alias and will be dropped after this block
let (sparse_sets, entities) = {
let world = self.world.world_mut();
@ -910,16 +925,20 @@ impl<'w> BundleSpawner<'w> {
// SAFETY: We have no outstanding mutable references to world as they were dropped
let mut deferred_world = unsafe { self.world.into_deferred() };
if archetype.has_on_add() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe { deferred_world.trigger_on_add(entity, bundle_info.iter_components()) };
}
if archetype.has_on_insert() {
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe { deferred_world.trigger_on_insert(entity, bundle_info.iter_components()) };
}
// SAFETY: `DeferredWorld` cannot provide mutable access to `Archetypes`.
let archetype = self.archetype.as_ref();
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(archetype, entity, bundle_info.iter_components());
if archetype.has_add_observer() {
deferred_world.trigger_observers(ON_ADD, entity, bundle_info.iter_components());
}
deferred_world.trigger_on_insert(archetype, entity, bundle_info.iter_components());
if archetype.has_insert_observer() {
deferred_world.trigger_observers(ON_INSERT, entity, bundle_info.iter_components());
}
};
location
}
@ -947,7 +966,7 @@ impl<'w> BundleSpawner<'w> {
#[inline]
pub(crate) unsafe fn flush_commands(&mut self) {
// SAFETY: pointers on self can be invalidated,
self.world.world_mut().flush_commands();
self.world.world_mut().flush();
}
}
@ -1223,13 +1242,13 @@ mod tests {
world
.register_component_hooks::<C>()
.on_add(|mut world, _, _| {
world.resource_mut::<R>().assert_order(2);
world.resource_mut::<R>().assert_order(3);
});
world
.register_component_hooks::<D>()
.on_add(|mut world, _, _| {
world.resource_mut::<R>().assert_order(3);
world.resource_mut::<R>().assert_order(2);
});
world.spawn(A).flush();

View file

@ -21,7 +21,6 @@ use std::{
borrow::Cow,
marker::PhantomData,
mem::needs_drop,
ops::Deref,
};
/// A data type that can be used to store data for an [entity].
@ -838,75 +837,6 @@ impl Components {
}
}
/// A wrapper over a mutable [`Components`] reference that allows for state initialization.
/// This can be obtained with [`World::component_initializer`].
pub struct ComponentInitializer<'w> {
pub(crate) components: &'w mut Components,
pub(crate) storages: &'w mut Storages,
}
impl<'w> Deref for ComponentInitializer<'w> {
type Target = Components;
fn deref(&self) -> &Components {
self.components
}
}
impl<'w> ComponentInitializer<'w> {
/// Initializes a component of type `T` with this instance.
/// If a component of this type has already been initialized, this will return
/// the ID of the pre-existing component.
///
/// # See also
///
/// * [`Components::component_id()`]
/// * [`Components::init_component_with_descriptor()`]
#[inline]
pub fn init_component<T: Component>(&mut self) -> ComponentId {
self.components.init_component::<T>(self.storages)
}
/// Initializes a component described by `descriptor`.
///
/// ## Note
///
/// If this method is called multiple times with identical descriptors, a distinct `ComponentId`
/// will be created for each one.
///
/// # See also
///
/// * [`Components::component_id()`]
/// * [`Components::init_component()`]
pub fn init_component_with_descriptor(
&mut self,
descriptor: ComponentDescriptor,
) -> ComponentId {
self.components
.init_component_with_descriptor(self.storages, descriptor)
}
/// Initializes a [`Resource`] of type `T` with this instance.
/// If a resource of this type has already been initialized, this will return
/// the ID of the pre-existing resource.
///
/// # See also
///
/// * [`Components::resource_id()`]
#[inline]
pub fn init_resource<T: Resource>(&mut self) -> ComponentId {
self.components.init_resource::<T>()
}
/// Initializes a [non-send resource](crate::system::NonSend) of type `T` with this instance.
/// If a resource of this type has already been initialized, this will return
/// the ID of the pre-existing resource.
#[inline]
pub fn init_non_send<T: Any>(&mut self) -> ComponentId {
self.components.init_non_send::<T>()
}
}
/// A value that tracks when a system ran relative to other systems.
/// This is used to power change detection.
///

View file

@ -38,7 +38,7 @@
mod map_entities;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
#[cfg(all(feature = "bevy_reflect", feature = "serde"))]
#[cfg(all(feature = "bevy_reflect", feature = "serialize"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
pub use map_entities::*;
@ -57,7 +57,7 @@ use crate::{
},
storage::{SparseSetIndex, TableId, TableRow},
};
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
use serde::{Deserialize, Serialize};
use std::{fmt, hash::Hash, mem, num::NonZeroU32, sync::atomic::Ordering};
@ -146,7 +146,7 @@ type IdCursor = isize;
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
#[cfg_attr(feature = "bevy_reflect", reflect_value(Hash, PartialEq))]
#[cfg_attr(
all(feature = "bevy_reflect", feature = "serde"),
all(feature = "bevy_reflect", feature = "serialize"),
reflect_value(Serialize, Deserialize)
)]
// Alignment repr necessary to allow LLVM to better output
@ -368,7 +368,7 @@ impl From<Entity> for Identifier {
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl Serialize for Entity {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -378,7 +378,7 @@ impl Serialize for Entity {
}
}
#[cfg(feature = "serde")]
#[cfg(feature = "serialize")]
impl<'de> Deserialize<'de> for Entity {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,107 @@
use crate::component::Component;
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use std::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
marker::PhantomData,
};
/// Something that "happens" and might be read / observed by app logic.
///
/// Events can be stored in an [`Events<E>`] resource
/// You can conveniently access events using the [`EventReader`] and [`EventWriter`] system parameter.
///
/// Events can also be "triggered" on a [`World`], which will then cause any [`Observer`] of that trigger to run.
///
/// This trait can be derived.
///
/// Events implement the [`Component`] type (and they automatically do when they are derived). Events are (generally)
/// not directly inserted as components. More often, the [`ComponentId`] is used to identify the event type within the
/// context of the ECS.
///
/// Events must be thread-safe.
///
/// [`World`]: crate::world::World
/// [`ComponentId`]: crate::component::ComponentId
/// [`Observer`]: crate::observer::Observer
#[diagnostic::on_unimplemented(
message = "`{Self}` is not an `Event`",
label = "invalid `Event`",
note = "consider annotating `{Self}` with `#[derive(Event)]`"
)]
pub trait Event: Component {}
/// An `EventId` uniquely identifies an event stored in a specific [`World`].
///
/// An `EventId` can among other things be used to trace the flow of an event from the point it was
/// sent to the point it was processed. `EventId`s increase monotonically by send order.
///
/// [`World`]: crate::world::World
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct EventId<E: Event> {
/// Uniquely identifies the event associated with this ID.
// This value corresponds to the order in which each event was added to the world.
pub id: usize,
#[cfg_attr(feature = "bevy_reflect", reflect(ignore))]
pub(super) _marker: PhantomData<E>,
}
impl<E: Event> Copy for EventId<E> {}
impl<E: Event> Clone for EventId<E> {
fn clone(&self) -> Self {
*self
}
}
impl<E: Event> fmt::Display for EventId<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<Self as fmt::Debug>::fmt(self, f)
}
}
impl<E: Event> fmt::Debug for EventId<E> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"event<{}>#{}",
std::any::type_name::<E>().split("::").last().unwrap(),
self.id,
)
}
}
impl<E: Event> PartialEq for EventId<E> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<E: Event> Eq for EventId<E> {}
impl<E: Event> PartialOrd for EventId<E> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<E: Event> Ord for EventId<E> {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl<E: Event> Hash for EventId<E> {
fn hash<H: Hasher>(&self, state: &mut H) {
Hash::hash(&self.id, state);
}
}
#[derive(Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub(crate) struct EventInstance<E: Event> {
pub event_id: EventId<E>,
pub event: E,
}

View file

@ -0,0 +1,406 @@
use crate as bevy_ecs;
use bevy_ecs::{
event::{Event, EventId, EventInstance, ManualEventReader},
system::Resource,
};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bevy_utils::detailed_trace;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
/// An event collection that represents the events that occurred within the last two
/// [`Events::update`] calls.
/// Events can be written to using an [`EventWriter`]
/// and are typically cheaply read using an [`EventReader`].
///
/// Each event can be consumed by multiple systems, in parallel,
/// with consumption tracked by the [`EventReader`] on a per-system basis.
///
/// If no [ordering](https://github.com/bevyengine/bevy/blob/main/examples/ecs/ecs_guide.rs)
/// is applied between writing and reading systems, there is a risk of a race condition.
/// This means that whether the events arrive before or after the next [`Events::update`] is unpredictable.
///
/// This collection is meant to be paired with a system that calls
/// [`Events::update`] exactly once per update/frame.
///
/// [`event_update_system`] is a system that does this, typically initialized automatically using
/// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event).
/// [`EventReader`]s are expected to read events from this collection at least once per loop/frame.
/// Events will persist across a single frame boundary and so ordering of event producers and
/// consumers is not critical (although poorly-planned ordering may cause accumulating lag).
/// If events are not handled by the end of the frame after they are updated, they will be
/// dropped silently.
///
/// # Example
/// ```
/// use bevy_ecs::event::{Event, Events};
///
/// #[derive(Event)]
/// struct MyEvent {
/// value: usize
/// }
///
/// // setup
/// let mut events = Events::<MyEvent>::default();
/// let mut reader = events.get_reader();
///
/// // run this once per update/frame
/// events.update();
///
/// // somewhere else: send an event
/// events.send(MyEvent { value: 1 });
///
/// // somewhere else: read the events
/// for event in reader.read(&events) {
/// assert_eq!(event.value, 1)
/// }
///
/// // events are only processed once per reader
/// assert_eq!(reader.read(&events).count(), 0);
/// ```
///
/// # Details
///
/// [`Events`] is implemented using a variation of a double buffer strategy.
/// Each call to [`update`](Events::update) swaps buffers and clears out the oldest one.
/// - [`EventReader`]s will read events from both buffers.
/// - [`EventReader`]s that read at least once per update will never drop events.
/// - [`EventReader`]s that read once within two updates might still receive some events
/// - [`EventReader`]s that read after two updates are guaranteed to drop all events that occurred
/// before those updates.
///
/// The buffers in [`Events`] will grow indefinitely if [`update`](Events::update) is never called.
///
/// An alternative call pattern would be to call [`update`](Events::update)
/// manually across frames to control when events are cleared.
/// This complicates consumption and risks ever-expanding memory usage if not cleaned up,
/// but can be done by adding your event as a resource instead of using
/// [`add_event`](https://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event).
///
/// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/event.rs)
/// [Example usage standalone.](https://github.com/bevyengine/bevy/blob/latest/crates/bevy_ecs/examples/events.rs)
///
#[derive(Debug, Resource)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub struct Events<E: Event> {
/// Holds the oldest still active events.
/// Note that `a.start_event_count + a.len()` should always be equal to `events_b.start_event_count`.
pub(crate) events_a: EventSequence<E>,
/// Holds the newer events.
pub(crate) events_b: EventSequence<E>,
pub(crate) event_count: usize,
}
// Derived Default impl would incorrectly require E: Default
impl<E: Event> Default for Events<E> {
fn default() -> Self {
Self {
events_a: Default::default(),
events_b: Default::default(),
event_count: Default::default(),
}
}
}
impl<E: Event> Events<E> {
/// Returns the index of the oldest event stored in the event buffer.
pub fn oldest_event_count(&self) -> usize {
self.events_a
.start_event_count
.min(self.events_b.start_event_count)
}
/// "Sends" an `event` by writing it to the current event buffer. [`EventReader`]s can then read
/// the event.
/// This method returns the [ID](`EventId`) of the sent `event`.
pub fn send(&mut self, event: E) -> EventId<E> {
let event_id = EventId {
id: self.event_count,
_marker: PhantomData,
};
detailed_trace!("Events::send() -> id: {}", event_id);
let event_instance = EventInstance { event_id, event };
self.events_b.push(event_instance);
self.event_count += 1;
event_id
}
/// Sends a list of `events` all at once, which can later be read by [`EventReader`]s.
/// This is more efficient than sending each event individually.
/// This method returns the [IDs](`EventId`) of the sent `events`.
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> {
let last_count = self.event_count;
self.extend(events);
SendBatchIds {
last_count,
event_count: self.event_count,
_marker: PhantomData,
}
}
/// Sends the default value of the event. Useful when the event is an empty struct.
/// This method returns the [ID](`EventId`) of the sent `event`.
pub fn send_default(&mut self) -> EventId<E>
where
E: Default,
{
self.send(Default::default())
}
/// Gets a new [`ManualEventReader`]. This will include all events already in the event buffers.
pub fn get_reader(&self) -> ManualEventReader<E> {
ManualEventReader::default()
}
/// Gets a new [`ManualEventReader`]. This will ignore all events already in the event buffers.
/// It will read all future events.
pub fn get_reader_current(&self) -> ManualEventReader<E> {
ManualEventReader {
last_event_count: self.event_count,
..Default::default()
}
}
/// Swaps the event buffers and clears the oldest event buffer. In general, this should be
/// called once per frame/update.
///
/// If you need access to the events that were removed, consider using [`Events::update_drain`].
pub fn update(&mut self) {
std::mem::swap(&mut self.events_a, &mut self.events_b);
self.events_b.clear();
self.events_b.start_event_count = self.event_count;
debug_assert_eq!(
self.events_a.start_event_count + self.events_a.len(),
self.events_b.start_event_count
);
}
/// Swaps the event buffers and drains the oldest event buffer, returning an iterator
/// of all events that were removed. In general, this should be called once per frame/update.
///
/// If you do not need to take ownership of the removed events, use [`Events::update`] instead.
#[must_use = "If you do not need the returned events, call .update() instead."]
pub fn update_drain(&mut self) -> impl Iterator<Item = E> + '_ {
std::mem::swap(&mut self.events_a, &mut self.events_b);
let iter = self.events_b.events.drain(..);
self.events_b.start_event_count = self.event_count;
debug_assert_eq!(
self.events_a.start_event_count + self.events_a.len(),
self.events_b.start_event_count
);
iter.map(|e| e.event)
}
#[inline]
fn reset_start_event_count(&mut self) {
self.events_a.start_event_count = self.event_count;
self.events_b.start_event_count = self.event_count;
}
/// Removes all events.
#[inline]
pub fn clear(&mut self) {
self.reset_start_event_count();
self.events_a.clear();
self.events_b.clear();
}
/// Returns the number of events currently stored in the event buffer.
#[inline]
pub fn len(&self) -> usize {
self.events_a.len() + self.events_b.len()
}
/// Returns true if there are no events currently stored in the event buffer.
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Creates a draining iterator that removes all events.
pub fn drain(&mut self) -> impl Iterator<Item = E> + '_ {
self.reset_start_event_count();
// Drain the oldest events first, then the newest
self.events_a
.drain(..)
.chain(self.events_b.drain(..))
.map(|i| i.event)
}
/// Iterates over events that happened since the last "update" call.
/// WARNING: You probably don't want to use this call. In most cases you should use an
/// [`EventReader`]. You should only use this if you know you only need to consume events
/// between the last `update()` call and your call to `iter_current_update_events`.
/// If events happen outside that window, they will not be handled. For example, any events that
/// happen after this call and before the next `update()` call will be dropped.
pub fn iter_current_update_events(&self) -> impl ExactSizeIterator<Item = &E> {
self.events_b.iter().map(|i| &i.event)
}
/// Get a specific event by id if it still exists in the events buffer.
pub fn get_event(&self, id: usize) -> Option<(&E, EventId<E>)> {
if id < self.oldest_id() {
return None;
}
let sequence = self.sequence(id);
let index = id.saturating_sub(sequence.start_event_count);
sequence
.get(index)
.map(|instance| (&instance.event, instance.event_id))
}
/// Oldest id still in the events buffer.
pub fn oldest_id(&self) -> usize {
self.events_a.start_event_count
}
/// Which event buffer is this event id a part of.
fn sequence(&self, id: usize) -> &EventSequence<E> {
if id < self.events_b.start_event_count {
&self.events_a
} else {
&self.events_b
}
}
}
impl<E: Event> Extend<E> for Events<E> {
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = E>,
{
let old_count = self.event_count;
let mut event_count = self.event_count;
let events = iter.into_iter().map(|event| {
let event_id = EventId {
id: event_count,
_marker: PhantomData,
};
event_count += 1;
EventInstance { event_id, event }
});
self.events_b.extend(events);
if old_count != event_count {
detailed_trace!(
"Events::extend() -> ids: ({}..{})",
self.event_count,
event_count
);
}
self.event_count = event_count;
}
}
#[derive(Debug)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
pub(crate) struct EventSequence<E: Event> {
pub(crate) events: Vec<EventInstance<E>>,
pub(crate) start_event_count: usize,
}
// Derived Default impl would incorrectly require E: Default
impl<E: Event> Default for EventSequence<E> {
fn default() -> Self {
Self {
events: Default::default(),
start_event_count: Default::default(),
}
}
}
impl<E: Event> Deref for EventSequence<E> {
type Target = Vec<EventInstance<E>>;
fn deref(&self) -> &Self::Target {
&self.events
}
}
impl<E: Event> DerefMut for EventSequence<E> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.events
}
}
/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch.
pub struct SendBatchIds<E> {
last_count: usize,
event_count: usize,
_marker: PhantomData<E>,
}
impl<E: Event> Iterator for SendBatchIds<E> {
type Item = EventId<E>;
fn next(&mut self) -> Option<Self::Item> {
if self.last_count >= self.event_count {
return None;
}
let result = Some(EventId {
id: self.last_count,
_marker: PhantomData,
});
self.last_count += 1;
result
}
}
impl<E: Event> ExactSizeIterator for SendBatchIds<E> {
fn len(&self) -> usize {
self.event_count.saturating_sub(self.last_count)
}
}
#[cfg(test)]
mod tests {
use crate::{self as bevy_ecs, event::Events};
use bevy_ecs_macros::Event;
#[test]
fn iter_current_update_events_iterates_over_current_events() {
#[derive(Event, Clone)]
struct TestEvent;
let mut test_events = Events::<TestEvent>::default();
// Starting empty
assert_eq!(test_events.len(), 0);
assert_eq!(test_events.iter_current_update_events().count(), 0);
test_events.update();
// Sending one event
test_events.send(TestEvent);
assert_eq!(test_events.len(), 1);
assert_eq!(test_events.iter_current_update_events().count(), 1);
test_events.update();
// Sending two events on the next frame
test_events.send(TestEvent);
test_events.send(TestEvent);
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
assert_eq!(test_events.iter_current_update_events().count(), 2);
test_events.update();
// Sending zero events
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
assert_eq!(test_events.iter_current_update_events().count(), 0);
}
}

View file

@ -0,0 +1,268 @@
use crate as bevy_ecs;
use bevy_ecs::{
batching::BatchingStrategy,
event::{Event, EventId, EventInstance, Events, ManualEventReader},
};
use bevy_utils::detailed_trace;
use std::{iter::Chain, slice::Iter};
/// An iterator that yields any unread events from an [`EventReader`] or [`ManualEventReader`].
#[derive(Debug)]
pub struct EventIterator<'a, E: Event> {
iter: EventIteratorWithId<'a, E>,
}
impl<'a, E: Event> Iterator for EventIterator<'a, E> {
type Item = &'a E;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|(event, _)| event)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
fn count(self) -> usize {
self.iter.count()
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
self.iter.last().map(|(event, _)| event)
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth(n).map(|(event, _)| event)
}
}
impl<'a, E: Event> ExactSizeIterator for EventIterator<'a, E> {
fn len(&self) -> usize {
self.iter.len()
}
}
/// An iterator that yields any unread events (and their IDs) from an [`EventReader`] or [`ManualEventReader`].
#[derive(Debug)]
pub struct EventIteratorWithId<'a, E: Event> {
reader: &'a mut ManualEventReader<E>,
chain: Chain<Iter<'a, EventInstance<E>>, Iter<'a, EventInstance<E>>>,
unread: usize,
}
impl<'a, E: Event> EventIteratorWithId<'a, E> {
/// Creates a new iterator that yields any `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut ManualEventReader<E>, events: &'a Events<E>) -> Self {
let a_index = reader
.last_event_count
.saturating_sub(events.events_a.start_event_count);
let b_index = reader
.last_event_count
.saturating_sub(events.events_b.start_event_count);
let a = events.events_a.get(a_index..).unwrap_or_default();
let b = events.events_b.get(b_index..).unwrap_or_default();
let unread_count = a.len() + b.len();
// Ensure `len` is implemented correctly
debug_assert_eq!(unread_count, reader.len(events));
reader.last_event_count = events.event_count - unread_count;
// Iterate the oldest first, then the newer events
let chain = a.iter().chain(b.iter());
Self {
reader,
chain,
unread: unread_count,
}
}
/// Iterate over only the events.
pub fn without_id(self) -> EventIterator<'a, E> {
EventIterator { iter: self }
}
}
impl<'a, E: Event> Iterator for EventIteratorWithId<'a, E> {
type Item = (&'a E, EventId<E>);
fn next(&mut self) -> Option<Self::Item> {
match self
.chain
.next()
.map(|instance| (&instance.event, instance.event_id))
{
Some(item) => {
detailed_trace!("EventReader::iter() -> {}", item.1);
self.reader.last_event_count += 1;
self.unread -= 1;
Some(item)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.chain.size_hint()
}
fn count(self) -> usize {
self.reader.last_event_count += self.unread;
self.unread
}
fn last(self) -> Option<Self::Item>
where
Self: Sized,
{
let EventInstance { event_id, event } = self.chain.last()?;
self.reader.last_event_count += self.unread;
Some((event, *event_id))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
if let Some(EventInstance { event_id, event }) = self.chain.nth(n) {
self.reader.last_event_count += n + 1;
self.unread -= n + 1;
Some((event, *event_id))
} else {
self.reader.last_event_count += self.unread;
self.unread = 0;
None
}
}
}
impl<'a, E: Event> ExactSizeIterator for EventIteratorWithId<'a, E> {
fn len(&self) -> usize {
self.unread
}
}
/// A parallel iterator over `Event`s.
#[derive(Debug)]
pub struct EventParIter<'a, E: Event> {
reader: &'a mut ManualEventReader<E>,
slices: [&'a [EventInstance<E>]; 2],
batching_strategy: BatchingStrategy,
}
impl<'a, E: Event> EventParIter<'a, E> {
/// Creates a new parallel iterator over `events` that have not yet been seen by `reader`.
pub fn new(reader: &'a mut ManualEventReader<E>, events: &'a Events<E>) -> Self {
let a_index = reader
.last_event_count
.saturating_sub(events.events_a.start_event_count);
let b_index = reader
.last_event_count
.saturating_sub(events.events_b.start_event_count);
let a = events.events_a.get(a_index..).unwrap_or_default();
let b = events.events_b.get(b_index..).unwrap_or_default();
let unread_count = a.len() + b.len();
// Ensure `len` is implemented correctly
debug_assert_eq!(unread_count, reader.len(events));
reader.last_event_count = events.event_count - unread_count;
Self {
reader,
slices: [a, b],
batching_strategy: BatchingStrategy::default(),
}
}
/// Changes the batching strategy used when iterating.
///
/// For more information on how this affects the resultant iteration, see
/// [`BatchingStrategy`].
pub fn batching_strategy(mut self, strategy: BatchingStrategy) -> Self {
self.batching_strategy = strategy;
self
}
/// Runs the provided closure for each unread event in parallel.
///
/// Unlike normal iteration, the event order is not guaranteed in any form.
///
/// # Panics
/// If the [`ComputeTaskPool`] is not initialized. If using this from an event reader that is being
/// initialized and run from the ECS scheduler, this should never panic.
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
pub fn for_each<FN: Fn(&'a E) + Send + Sync + Clone>(self, func: FN) {
self.for_each_with_id(move |e, _| func(e));
}
/// Runs the provided closure for each unread event in parallel, like [`for_each`](Self::for_each),
/// but additionally provides the `EventId` to the closure.
///
/// Note that the order of iteration is not guaranteed, but `EventId`s are ordered by send order.
///
/// # Panics
/// If the [`ComputeTaskPool`] is not initialized. If using this from an event reader that is being
/// initialized and run from the ECS scheduler, this should never panic.
///
/// [`ComputeTaskPool`]: bevy_tasks::ComputeTaskPool
pub fn for_each_with_id<FN: Fn(&'a E, EventId<E>) + Send + Sync + Clone>(self, func: FN) {
#[cfg(any(target_arch = "wasm32", not(feature = "multi_threaded")))]
{
self.into_iter().for_each(|(e, i)| func(e, i));
}
#[cfg(all(not(target_arch = "wasm32"), feature = "multi_threaded"))]
{
let pool = bevy_tasks::ComputeTaskPool::get();
let thread_count = pool.thread_num();
if thread_count <= 1 {
return self.into_iter().for_each(|(e, i)| func(e, i));
}
let batch_size = self
.batching_strategy
.calc_batch_size(|| self.len(), thread_count);
let chunks = self.slices.map(|s| s.chunks_exact(batch_size));
let remainders = chunks.each_ref().map(|c| c.remainder());
pool.scope(|scope| {
for batch in chunks.into_iter().flatten().chain(remainders) {
let func = func.clone();
scope.spawn(async move {
for event in batch {
func(&event.event, event.event_id);
}
});
}
});
}
}
/// Returns the number of [`Event`]s to be iterated.
pub fn len(&self) -> usize {
self.slices.iter().map(|s| s.len()).sum()
}
/// Returns [`true`] if there are no events remaining in this iterator.
pub fn is_empty(&self) -> bool {
self.slices.iter().all(|x| x.is_empty())
}
}
impl<'a, E: Event> IntoIterator for EventParIter<'a, E> {
type IntoIter = EventIteratorWithId<'a, E>;
type Item = <Self::IntoIter as Iterator>::Item;
fn into_iter(self) -> Self::IntoIter {
let EventParIter {
reader,
slices: [a, b],
..
} = self;
let unread = a.len() + b.len();
let chain = a.iter().chain(b);
EventIteratorWithId {
reader,
chain,
unread,
}
}
}

View file

@ -0,0 +1,450 @@
//! Event handling types.
mod base;
mod collections;
mod iterators;
mod reader;
mod registry;
mod update;
mod writer;
pub(crate) use base::EventInstance;
pub use base::{Event, EventId};
pub use bevy_ecs_macros::Event;
pub use collections::{Events, SendBatchIds};
pub use iterators::{EventIterator, EventIteratorWithId, EventParIter};
pub use reader::{EventReader, ManualEventReader};
pub use registry::{EventRegistry, ShouldUpdateEvents};
pub use update::{
event_update_condition, event_update_system, signal_event_update_system, EventUpdates,
};
pub use writer::EventWriter;
#[cfg(test)]
mod tests {
use crate as bevy_ecs;
use bevy_ecs::{event::*, system::assert_is_read_only_system};
use bevy_ecs_macros::Event;
#[derive(Event, Copy, Clone, PartialEq, Eq, Debug)]
struct TestEvent {
i: usize,
}
#[test]
fn test_events() {
let mut events = Events::<TestEvent>::default();
let event_0 = TestEvent { i: 0 };
let event_1 = TestEvent { i: 1 };
let event_2 = TestEvent { i: 2 };
// this reader will miss event_0 and event_1 because it wont read them over the course of
// two updates
let mut reader_missed: ManualEventReader<TestEvent> = events.get_reader();
let mut reader_a: ManualEventReader<TestEvent> = events.get_reader();
events.send(event_0);
assert_eq!(
get_events(&events, &mut reader_a),
vec![event_0],
"reader_a created before event receives event"
);
assert_eq!(
get_events(&events, &mut reader_a),
vec![],
"second iteration of reader_a created before event results in zero events"
);
let mut reader_b: ManualEventReader<TestEvent> = events.get_reader();
assert_eq!(
get_events(&events, &mut reader_b),
vec![event_0],
"reader_b created after event receives event"
);
assert_eq!(
get_events(&events, &mut reader_b),
vec![],
"second iteration of reader_b created after event results in zero events"
);
events.send(event_1);
let mut reader_c = events.get_reader();
assert_eq!(
get_events(&events, &mut reader_c),
vec![event_0, event_1],
"reader_c created after two events receives both events"
);
assert_eq!(
get_events(&events, &mut reader_c),
vec![],
"second iteration of reader_c created after two event results in zero events"
);
assert_eq!(
get_events(&events, &mut reader_a),
vec![event_1],
"reader_a receives next unread event"
);
events.update();
let mut reader_d = events.get_reader();
events.send(event_2);
assert_eq!(
get_events(&events, &mut reader_a),
vec![event_2],
"reader_a receives event created after update"
);
assert_eq!(
get_events(&events, &mut reader_b),
vec![event_1, event_2],
"reader_b receives events created before and after update"
);
assert_eq!(
get_events(&events, &mut reader_d),
vec![event_0, event_1, event_2],
"reader_d receives all events created before and after update"
);
events.update();
assert_eq!(
get_events(&events, &mut reader_missed),
vec![event_2],
"reader_missed missed events unread after two update() calls"
);
}
fn get_events<E: Event + Clone>(
events: &Events<E>,
reader: &mut ManualEventReader<E>,
) -> Vec<E> {
reader.read(events).cloned().collect::<Vec<E>>()
}
#[derive(Event, PartialEq, Eq, Debug)]
struct E(usize);
fn events_clear_and_read_impl(clear_func: impl FnOnce(&mut Events<E>)) {
let mut events = Events::<E>::default();
let mut reader = events.get_reader();
assert!(reader.read(&events).next().is_none());
events.send(E(0));
assert_eq!(*reader.read(&events).next().unwrap(), E(0));
assert_eq!(reader.read(&events).next(), None);
events.send(E(1));
clear_func(&mut events);
assert!(reader.read(&events).next().is_none());
events.send(E(2));
events.update();
events.send(E(3));
assert!(reader.read(&events).eq([E(2), E(3)].iter()));
}
#[test]
fn test_events_clear_and_read() {
events_clear_and_read_impl(|events| events.clear());
}
#[test]
fn test_events_drain_and_read() {
events_clear_and_read_impl(|events| {
assert!(events.drain().eq(vec![E(0), E(1)].into_iter()));
});
}
#[test]
fn test_events_extend_impl() {
let mut events = Events::<TestEvent>::default();
let mut reader = events.get_reader();
events.extend(vec![TestEvent { i: 0 }, TestEvent { i: 1 }]);
assert!(reader
.read(&events)
.eq([TestEvent { i: 0 }, TestEvent { i: 1 }].iter()));
}
#[test]
fn test_events_empty() {
let mut events = Events::<TestEvent>::default();
assert!(events.is_empty());
events.send(TestEvent { i: 0 });
assert!(!events.is_empty());
events.update();
assert!(!events.is_empty());
// events are only empty after the second call to update
// due to double buffering.
events.update();
assert!(events.is_empty());
}
#[test]
fn test_event_reader_len_empty() {
let events = Events::<TestEvent>::default();
assert_eq!(events.get_reader().len(&events), 0);
assert!(events.get_reader().is_empty(&events));
}
#[test]
fn test_event_reader_len_filled() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
assert_eq!(events.get_reader().len(&events), 1);
assert!(!events.get_reader().is_empty(&events));
}
#[test]
fn test_event_iter_len_updated() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
events.send(TestEvent { i: 1 });
events.send(TestEvent { i: 2 });
let mut reader = events.get_reader();
let mut iter = reader.read(&events);
assert_eq!(iter.len(), 3);
iter.next();
assert_eq!(iter.len(), 2);
iter.next();
assert_eq!(iter.len(), 1);
iter.next();
assert_eq!(iter.len(), 0);
}
#[test]
fn test_event_reader_len_current() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
let reader = events.get_reader_current();
dbg!(&reader);
dbg!(&events);
assert!(reader.is_empty(&events));
events.send(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 1);
assert!(!reader.is_empty(&events));
}
#[test]
fn test_event_reader_len_update() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
events.send(TestEvent { i: 0 });
let reader = events.get_reader();
assert_eq!(reader.len(&events), 2);
events.update();
events.send(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 3);
events.update();
assert_eq!(reader.len(&events), 1);
events.update();
assert!(reader.is_empty(&events));
}
#[test]
fn test_event_reader_clear() {
use bevy_ecs::prelude::*;
let mut world = World::new();
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
world.insert_resource(events);
let mut reader = IntoSystem::into_system(|mut events: EventReader<TestEvent>| -> bool {
if !events.is_empty() {
events.clear();
false
} else {
true
}
});
reader.initialize(&mut world);
let is_empty = reader.run((), &mut world);
assert!(!is_empty, "EventReader should not be empty");
let is_empty = reader.run((), &mut world);
assert!(is_empty, "EventReader should be empty");
}
#[test]
fn test_update_drain() {
let mut events = Events::<TestEvent>::default();
let mut reader = events.get_reader();
events.send(TestEvent { i: 0 });
events.send(TestEvent { i: 1 });
assert_eq!(reader.read(&events).count(), 2);
let mut old_events = Vec::from_iter(events.update_drain());
assert!(old_events.is_empty());
events.send(TestEvent { i: 2 });
assert_eq!(reader.read(&events).count(), 1);
old_events.extend(events.update_drain());
assert_eq!(old_events.len(), 2);
old_events.extend(events.update_drain());
assert_eq!(
old_events,
&[TestEvent { i: 0 }, TestEvent { i: 1 }, TestEvent { i: 2 }]
);
}
#[allow(clippy::iter_nth_zero)]
#[test]
fn test_event_iter_nth() {
use bevy_ecs::prelude::*;
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
world.send_event(TestEvent { i: 0 });
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
world.send_event(TestEvent { i: 4 });
let mut schedule = Schedule::default();
schedule.add_systems(|mut events: EventReader<TestEvent>| {
let mut iter = events.read();
assert_eq!(iter.next(), Some(&TestEvent { i: 0 }));
assert_eq!(iter.nth(2), Some(&TestEvent { i: 3 }));
assert_eq!(iter.nth(1), None);
assert!(events.is_empty());
});
schedule.run(&mut world);
}
#[test]
fn test_event_iter_last() {
use bevy_ecs::prelude::*;
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
let mut reader =
IntoSystem::into_system(|mut events: EventReader<TestEvent>| -> Option<TestEvent> {
events.read().last().copied()
});
reader.initialize(&mut world);
let last = reader.run((), &mut world);
assert!(last.is_none(), "EventReader should be empty");
world.send_event(TestEvent { i: 0 });
let last = reader.run((), &mut world);
assert_eq!(last, Some(TestEvent { i: 0 }));
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
let last = reader.run((), &mut world);
assert_eq!(last, Some(TestEvent { i: 3 }));
let last = reader.run((), &mut world);
assert!(last.is_none(), "EventReader should be empty");
}
#[derive(Event, Clone, PartialEq, Debug, Default)]
struct EmptyTestEvent;
#[test]
fn test_firing_empty_event() {
let mut events = Events::<EmptyTestEvent>::default();
events.send_default();
let mut reader = events.get_reader();
assert_eq!(get_events(&events, &mut reader), vec![EmptyTestEvent]);
}
#[test]
fn ensure_reader_readonly() {
fn reader_system(_: EventReader<EmptyTestEvent>) {}
assert_is_read_only_system(reader_system);
}
#[test]
fn test_send_events_ids() {
let mut events = Events::<TestEvent>::default();
let event_0 = TestEvent { i: 0 };
let event_1 = TestEvent { i: 1 };
let event_2 = TestEvent { i: 2 };
let event_0_id = events.send(event_0);
assert_eq!(
events.get_event(event_0_id.id),
Some((&event_0, event_0_id)),
"Getting a sent event by ID should return the original event"
);
let mut event_ids = events.send_batch([event_1, event_2]);
let event_id = event_ids.next().expect("Event 1 must have been sent");
assert_eq!(
events.get_event(event_id.id),
Some((&event_1, event_id)),
"Getting a sent event by ID should return the original event"
);
let event_id = event_ids.next().expect("Event 2 must have been sent");
assert_eq!(
events.get_event(event_id.id),
Some((&event_2, event_id)),
"Getting a sent event by ID should return the original event"
);
assert!(
event_ids.next().is_none(),
"Only sent two events; got more than two IDs"
);
}
#[cfg(feature = "multi_threaded")]
#[test]
fn test_events_par_iter() {
use std::{collections::HashSet, sync::mpsc};
use crate::prelude::*;
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
for i in 0..100 {
world.send_event(TestEvent { i });
}
let mut schedule = Schedule::default();
schedule.add_systems(|mut events: EventReader<TestEvent>| {
let (tx, rx) = mpsc::channel();
events.par_read().for_each(|event| {
tx.send(event.i).unwrap();
});
drop(tx);
let observed: HashSet<_> = rx.into_iter().collect();
assert_eq!(observed, HashSet::from_iter(0..100));
});
schedule.run(&mut world);
}
// Peak tests
}

View file

@ -0,0 +1,224 @@
use crate as bevy_ecs;
use bevy_ecs::{
event::{Event, EventIterator, EventIteratorWithId, EventParIter, Events},
system::{Local, Res, SystemParam},
};
use std::marker::PhantomData;
/// Reads events of type `T` in order and tracks which events have already been read.
///
/// # Concurrency
///
/// Unlike [`EventWriter<T>`], systems with `EventReader<T>` param can be executed concurrently
/// (but not concurrently with `EventWriter<T>` systems for the same event type).
#[derive(SystemParam, Debug)]
pub struct EventReader<'w, 's, E: Event> {
pub(super) reader: Local<'s, ManualEventReader<E>>,
events: Res<'w, Events<E>>,
}
impl<'w, 's, E: Event> EventReader<'w, 's, E> {
/// Iterates over the events this [`EventReader`] has not seen yet. This updates the
/// [`EventReader`]'s event counter, which means subsequent event reads will not include events
/// that happened before now.
pub fn read(&mut self) -> EventIterator<'_, E> {
self.reader.read(&self.events)
}
/// Like [`read`](Self::read), except also returning the [`EventId`] of the events.
pub fn read_with_id(&mut self) -> EventIteratorWithId<'_, E> {
self.reader.read_with_id(&self.events)
}
/// Returns a parallel iterator over the events this [`EventReader`] has not seen yet.
/// See also [`for_each`](EventParIter::for_each).
///
/// # Example
/// ```
/// # use bevy_ecs::prelude::*;
/// # use std::sync::atomic::{AtomicUsize, Ordering};
///
/// #[derive(Event)]
/// struct MyEvent {
/// value: usize,
/// }
///
/// #[derive(Resource, Default)]
/// struct Counter(AtomicUsize);
///
/// // setup
/// let mut world = World::new();
/// world.init_resource::<Events<MyEvent>>();
/// world.insert_resource(Counter::default());
///
/// let mut schedule = Schedule::default();
/// schedule.add_systems(|mut events: EventReader<MyEvent>, counter: Res<Counter>| {
/// events.par_read().for_each(|MyEvent { value }| {
/// counter.0.fetch_add(*value, Ordering::Relaxed);
/// });
/// });
/// for value in 0..100 {
/// world.send_event(MyEvent { value });
/// }
/// schedule.run(&mut world);
/// let Counter(counter) = world.remove_resource::<Counter>().unwrap();
/// // all events were processed
/// assert_eq!(counter.into_inner(), 4950);
/// ```
///
pub fn par_read(&mut self) -> EventParIter<'_, E> {
self.reader.par_read(&self.events)
}
/// Determines the number of events available to be read from this [`EventReader`] without consuming any.
pub fn len(&self) -> usize {
self.reader.len(&self.events)
}
/// Returns `true` if there are no events available to read.
///
/// # Example
///
/// The following example shows a useful pattern where some behavior is triggered if new events are available.
/// [`EventReader::clear()`] is used so the same events don't re-trigger the behavior the next time the system runs.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// #
/// #[derive(Event)]
/// struct CollisionEvent;
///
/// fn play_collision_sound(mut events: EventReader<CollisionEvent>) {
/// if !events.is_empty() {
/// events.clear();
/// // Play a sound
/// }
/// }
/// # bevy_ecs::system::assert_is_system(play_collision_sound);
/// ```
pub fn is_empty(&self) -> bool {
self.reader.is_empty(&self.events)
}
/// Consumes all available events.
///
/// This means these events will not appear in calls to [`EventReader::read()`] or
/// [`EventReader::read_with_id()`] and [`EventReader::is_empty()`] will return `true`.
///
/// For usage, see [`EventReader::is_empty()`].
pub fn clear(&mut self) {
self.reader.clear(&self.events);
}
}
/// Stores the state for an [`EventReader`].
///
/// Access to the [`Events<E>`] resource is required to read any incoming events.
///
/// In almost all cases, you should just use an [`EventReader`],
/// which will automatically manage the state for you.
///
/// However, this type can be useful if you need to manually track events,
/// such as when you're attempting to send and receive events of the same type in the same system.
///
/// # Example
///
/// ```
/// use bevy_ecs::prelude::*;
/// use bevy_ecs::event::{Event, Events, ManualEventReader};
///
/// #[derive(Event, Clone, Debug)]
/// struct MyEvent;
///
/// /// A system that both sends and receives events using a [`Local`] [`ManualEventReader`].
/// fn send_and_receive_manual_event_reader(
/// // The `Local` `SystemParam` stores state inside the system itself, rather than in the world.
/// // `ManualEventReader<T>` is the internal state of `EventReader<T>`, which tracks which events have been seen.
/// mut local_event_reader: Local<ManualEventReader<MyEvent>>,
/// // We can access the `Events` resource mutably, allowing us to both read and write its contents.
/// mut events: ResMut<Events<MyEvent>>,
/// ) {
/// // We must collect the events to resend, because we can't mutate events while we're iterating over the events.
/// let mut events_to_resend = Vec::new();
///
/// for event in local_event_reader.read(&events) {
/// events_to_resend.push(event.clone());
/// }
///
/// for event in events_to_resend {
/// events.send(MyEvent);
/// }
/// }
///
/// # bevy_ecs::system::assert_is_system(send_and_receive_manual_event_reader);
/// ```
#[derive(Debug)]
pub struct ManualEventReader<E: Event> {
pub(super) last_event_count: usize,
pub(super) _marker: PhantomData<E>,
}
impl<E: Event> Default for ManualEventReader<E> {
fn default() -> Self {
ManualEventReader {
last_event_count: 0,
_marker: Default::default(),
}
}
}
impl<E: Event> Clone for ManualEventReader<E> {
fn clone(&self) -> Self {
ManualEventReader {
last_event_count: self.last_event_count,
_marker: PhantomData,
}
}
}
#[allow(clippy::len_without_is_empty)] // Check fails since the is_empty implementation has a signature other than `(&self) -> bool`
impl<E: Event> ManualEventReader<E> {
/// See [`EventReader::read`]
pub fn read<'a>(&'a mut self, events: &'a Events<E>) -> EventIterator<'a, E> {
self.read_with_id(events).without_id()
}
/// See [`EventReader::read_with_id`]
pub fn read_with_id<'a>(&'a mut self, events: &'a Events<E>) -> EventIteratorWithId<'a, E> {
EventIteratorWithId::new(self, events)
}
/// See [`EventReader::par_read`]
pub fn par_read<'a>(&'a mut self, events: &'a Events<E>) -> EventParIter<'a, E> {
EventParIter::new(self, events)
}
/// See [`EventReader::len`]
pub fn len(&self, events: &Events<E>) -> usize {
// The number of events in this reader is the difference between the most recent event
// and the last event seen by it. This will be at most the number of events contained
// with the events (any others have already been dropped)
// TODO: Warn when there are dropped events, or return e.g. a `Result<usize, (usize, usize)>`
events
.event_count
.saturating_sub(self.last_event_count)
.min(events.len())
}
/// Amount of events we missed.
pub fn missed_events(&self, events: &Events<E>) -> usize {
events
.oldest_event_count()
.saturating_sub(self.last_event_count)
}
/// See [`EventReader::is_empty()`]
pub fn is_empty(&self, events: &Events<E>) -> bool {
self.len(events) == 0
}
/// See [`EventReader::clear()`]
pub fn clear(&mut self, events: &Events<E>) {
self.last_event_count = events.event_count;
}
}

View file

@ -0,0 +1,80 @@
use crate as bevy_ecs;
use bevy_ecs::{
change_detection::{DetectChangesMut, MutUntyped},
component::{ComponentId, Tick},
event::{Event, Events},
system::Resource,
world::World,
};
#[doc(hidden)]
struct RegisteredEvent {
component_id: ComponentId,
// Required to flush the secondary buffer and drop events even if left unchanged.
previously_updated: bool,
// SAFETY: The component ID and the function must be used to fetch the Events<T> resource
// of the same type initialized in `register_event`, or improper type casts will occur.
update: unsafe fn(MutUntyped),
}
/// A registry of all of the [`Events`] in the [`World`], used by [`event_update_system`](crate::event::update::event_update_system)
/// to update all of the events.
#[derive(Resource, Default)]
pub struct EventRegistry {
/// Should the events be updated?
///
/// This field is generally automatically updated by the [`signal_event_update_system`](crate::event::update::signal_event_update_system).
pub should_update: ShouldUpdateEvents,
event_updates: Vec<RegisteredEvent>,
}
/// Controls whether or not the events in an [`EventRegistry`] should be updated.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
pub enum ShouldUpdateEvents {
/// Without any fixed timestep, events should always be updated each frame.
#[default]
Always,
/// We need to wait until at least one pass of the fixed update schedules to update the events.
Waiting,
/// At least one pass of the fixed update schedules has occurred, and the events are ready to be updated.
Ready,
}
impl EventRegistry {
/// Registers an event type to be updated.
pub fn register_event<T: Event>(world: &mut World) {
// By initializing the resource here, we can be sure that it is present,
// and receive the correct, up-to-date `ComponentId` even if it was previously removed.
let component_id = world.init_resource::<Events<T>>();
let mut registry = world.get_resource_or_insert_with(Self::default);
registry.event_updates.push(RegisteredEvent {
component_id,
previously_updated: false,
update: |ptr| {
// SAFETY: The resource was initialized with the type Events<T>.
unsafe { ptr.with_type::<Events<T>>() }
.bypass_change_detection()
.update();
},
});
}
/// Updates all of the registered events in the World.
pub fn run_updates(&mut self, world: &mut World, last_change_tick: Tick) {
for registered_event in &mut self.event_updates {
// Bypass the type ID -> Component ID lookup with the cached component ID.
if let Some(events) = world.get_resource_mut_by_id(registered_event.component_id) {
let has_changed = events.has_changed_since(last_change_tick);
if registered_event.previously_updated || has_changed {
// SAFETY: The update function pointer is called with the resource
// fetched from the same component ID.
unsafe { (registered_event.update)(events) };
// Always set to true if the events have changed, otherwise disable running on the second invocation
// to wait for more changes.
registered_event.previously_updated =
has_changed || !registered_event.previously_updated;
}
}
}
}
}

View file

@ -0,0 +1,62 @@
use crate as bevy_ecs;
use bevy_ecs::{
change_detection::Mut,
component::Tick,
event::EventRegistry,
system::{Local, Res, ResMut},
world::World,
};
use bevy_ecs_macros::SystemSet;
#[cfg(feature = "bevy_reflect")]
use std::hash::Hash;
use super::registry::ShouldUpdateEvents;
#[doc(hidden)]
#[derive(SystemSet, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EventUpdates;
/// Signals the [`event_update_system`] to run after `FixedUpdate` systems.
///
/// This will change the behavior of the [`EventRegistry`] to only run after a fixed update cycle has passed.
/// Normally, this will simply run every frame.
pub fn signal_event_update_system(signal: Option<ResMut<EventRegistry>>) {
if let Some(mut registry) = signal {
registry.should_update = ShouldUpdateEvents::Ready;
}
}
/// A system that calls [`Events::update`] on all registered [`Events`] in the world.
pub fn event_update_system(world: &mut World, mut last_change_tick: Local<Tick>) {
if world.contains_resource::<EventRegistry>() {
world.resource_scope(|world, mut registry: Mut<EventRegistry>| {
registry.run_updates(world, *last_change_tick);
registry.should_update = match registry.should_update {
// If we're always updating, keep doing so.
ShouldUpdateEvents::Always => ShouldUpdateEvents::Always,
// Disable the system until signal_event_update_system runs again.
ShouldUpdateEvents::Waiting | ShouldUpdateEvents::Ready => {
ShouldUpdateEvents::Waiting
}
};
});
}
*last_change_tick = world.change_tick();
}
/// A run condition for [`event_update_system`].
///
/// If [`signal_event_update_system`] has been run at least once,
/// we will wait for it to be run again before updating the events.
///
/// Otherwise, we will always update the events.
pub fn event_update_condition(maybe_signal: Option<Res<EventRegistry>>) -> bool {
match maybe_signal {
Some(signal) => match signal.should_update {
ShouldUpdateEvents::Always | ShouldUpdateEvents::Ready => true,
ShouldUpdateEvents::Waiting => false,
},
None => true,
}
}

View file

@ -0,0 +1,95 @@
use crate as bevy_ecs;
use bevy_ecs::{
event::{Event, EventId, Events, SendBatchIds},
system::{ResMut, SystemParam},
};
/// Sends events of type `T`.
///
/// # Usage
///
/// `EventWriter`s are usually declared as a [`SystemParam`].
/// ```
/// # use bevy_ecs::prelude::*;
///
/// #[derive(Event)]
/// pub struct MyEvent; // Custom event type.
/// fn my_system(mut writer: EventWriter<MyEvent>) {
/// writer.send(MyEvent);
/// }
///
/// # bevy_ecs::system::assert_is_system(my_system);
/// ```
/// # Observers
///
/// "Buffered" Events, such as those sent directly in [`Events`] or sent using [`EventWriter`], do _not_ automatically
/// trigger any [`Observer`]s watching for that event, as each [`Event`] has different requirements regarding _if_ it will
/// be triggered, and if so, _when_ it will be triggered in the schedule.
///
/// # Concurrency
///
/// `EventWriter` param has [`ResMut<Events<T>>`](Events) inside. So two systems declaring `EventWriter<T>` params
/// for the same event type won't be executed concurrently.
///
/// # Untyped events
///
/// `EventWriter` can only send events of one specific type, which must be known at compile-time.
/// This is not a problem most of the time, but you may find a situation where you cannot know
/// ahead of time every kind of event you'll need to send. In this case, you can use the "type-erased event" pattern.
///
/// ```
/// # use bevy_ecs::{prelude::*, event::Events};
/// # #[derive(Event)]
/// # pub struct MyEvent;
/// fn send_untyped(mut commands: Commands) {
/// // Send an event of a specific type without having to declare that
/// // type as a SystemParam.
/// //
/// // Effectively, we're just moving the type parameter from the /type/ to the /method/,
/// // which allows one to do all kinds of clever things with type erasure, such as sending
/// // custom events to unknown 3rd party plugins (modding API).
/// //
/// // NOTE: the event won't actually be sent until commands get applied during
/// // apply_deferred.
/// commands.add(|w: &mut World| {
/// w.send_event(MyEvent);
/// });
/// }
/// ```
/// Note that this is considered *non-idiomatic*, and should only be used when `EventWriter` will not work.
///
/// [`Observer`]: crate::observer::Observer
#[derive(SystemParam)]
pub struct EventWriter<'w, E: Event> {
events: ResMut<'w, Events<E>>,
}
impl<'w, E: Event> EventWriter<'w, E> {
/// Sends an `event`, which can later be read by [`EventReader`]s.
/// This method returns the [ID](`EventId`) of the sent `event`.
///
/// See [`Events`] for details.
pub fn send(&mut self, event: E) -> EventId<E> {
self.events.send(event)
}
/// Sends a list of `events` all at once, which can later be read by [`EventReader`]s.
/// This is more efficient than sending each event individually.
/// This method returns the [IDs](`EventId`) of the sent `events`.
///
/// See [`Events`] for details.
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> {
self.events.send_batch(events)
}
/// Sends the default value of the event. Useful when the event is an empty struct.
/// This method returns the [ID](`EventId`) of the sent `event`.
///
/// See [`Events`] for details.
pub fn send_default(&mut self) -> EventId<E>
where
E: Default,
{
self.events.send_default()
}
}

View file

@ -21,6 +21,7 @@ pub mod event;
pub mod identifier;
pub mod intern;
pub mod label;
pub mod observer;
pub mod query;
#[cfg(feature = "bevy_reflect")]
pub mod reflect;
@ -46,6 +47,7 @@ pub mod prelude {
component::Component,
entity::{Entity, EntityMapper},
event::{Event, EventReader, EventWriter, Events},
observer::{Observer, Trigger},
query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without},
removal_detection::RemovedComponents,
schedule::{
@ -57,7 +59,9 @@ pub mod prelude {
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemBuilder,
SystemParamFunction,
},
world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World},
world::{
EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, World,
},
};
}

View file

@ -0,0 +1,42 @@
use crate::{
component::{Component, ComponentHooks, StorageType},
entity::Entity,
observer::ObserverState,
};
/// Tracks a list of entity observers for the [`Entity`] [`ObservedBy`] is added to.
#[derive(Default)]
pub(crate) struct ObservedBy(pub(crate) Vec<Entity>);
impl Component for ObservedBy {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_remove(|mut world, entity, _| {
let observed_by = {
let mut component = world.get_mut::<ObservedBy>(entity).unwrap();
std::mem::take(&mut component.0)
};
for e in observed_by {
let (total_entities, despawned_watched_entities) = {
let Some(mut entity_mut) = world.get_entity_mut(e) else {
continue;
};
let Some(mut state) = entity_mut.get_mut::<ObserverState>() else {
continue;
};
state.despawned_watched_entities += 1;
(
state.descriptor.entities.len(),
state.despawned_watched_entities as usize,
)
};
// Despawn Observer if it has no more active sources.
if total_entities == despawned_watched_entities {
world.commands().entity(e).despawn();
}
}
});
}
}

View file

@ -0,0 +1,658 @@
//! Types for creating and storing [`Observer`]s
mod entity_observer;
mod runner;
mod trigger_event;
pub use runner::*;
pub use trigger_event::*;
use crate::observer::entity_observer::ObservedBy;
use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*};
use crate::{component::ComponentId, prelude::*, world::DeferredWorld};
use bevy_ptr::Ptr;
use bevy_utils::{EntityHashMap, HashMap};
use std::marker::PhantomData;
/// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the
/// [`Event`] data itself. If it was triggered for a specific [`Entity`], it includes that as well.
pub struct Trigger<'w, E, B: Bundle = ()> {
event: &'w mut E,
trigger: ObserverTrigger,
_marker: PhantomData<B>,
}
impl<'w, E, B: Bundle> Trigger<'w, E, B> {
/// Creates a new trigger for the given event and observer information.
pub fn new(event: &'w mut E, trigger: ObserverTrigger) -> Self {
Self {
event,
trigger,
_marker: PhantomData,
}
}
/// Returns the event type of this trigger.
pub fn event_type(&self) -> ComponentId {
self.trigger.event_type
}
/// Returns a reference to the triggered event.
pub fn event(&self) -> &E {
self.event
}
/// Returns a mutable reference to the triggered event.
pub fn event_mut(&mut self) -> &mut E {
self.event
}
/// Returns a pointer to the triggered event.
pub fn event_ptr(&self) -> Ptr {
Ptr::from(&self.event)
}
/// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`].
pub fn entity(&self) -> Entity {
self.trigger.entity
}
}
/// A description of what an [`Observer`] observes.
#[derive(Default, Clone)]
pub struct ObserverDescriptor {
/// The events the observer is watching.
events: Vec<ComponentId>,
/// The components the observer is watching.
components: Vec<ComponentId>,
/// The entities the observer is watching.
entities: Vec<Entity>,
}
impl ObserverDescriptor {
/// Add the given `triggers` to the descriptor.
pub fn with_triggers(mut self, triggers: Vec<ComponentId>) -> Self {
self.events = triggers;
self
}
/// Add the given `components` to the descriptor.
pub fn with_components(mut self, components: Vec<ComponentId>) -> Self {
self.components = components;
self
}
/// Add the given `entities` to the descriptor.
pub fn with_entities(mut self, entities: Vec<Entity>) -> Self {
self.entities = entities;
self
}
pub(crate) fn merge(&mut self, descriptor: &ObserverDescriptor) {
self.events.extend(descriptor.events.iter().copied());
self.components
.extend(descriptor.components.iter().copied());
self.entities.extend(descriptor.entities.iter().copied());
}
}
/// Event trigger metadata for a given [`Observer`],
#[derive(Debug)]
pub struct ObserverTrigger {
/// The [`Entity`] of the observer handling the trigger.
pub observer: Entity,
/// The [`ComponentId`] the trigger targeted.
pub event_type: ComponentId,
/// The entity the trigger targeted.
pub entity: Entity,
}
// Map between an observer entity and its runner
type ObserverMap = EntityHashMap<Entity, ObserverRunner>;
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component.
#[derive(Default, Debug)]
pub struct CachedComponentObservers {
// Observers listening to triggers targeting this component
map: ObserverMap,
// Observers listening to triggers targeting this component on a specific entity
entity_map: EntityHashMap<Entity, ObserverMap>,
}
/// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger.
#[derive(Default, Debug)]
pub struct CachedObservers {
// Observers listening for any time this trigger is fired
map: ObserverMap,
// Observers listening for this trigger fired at a specific component
component_observers: HashMap<ComponentId, CachedComponentObservers>,
// Observers listening for this trigger fired at a specific entity
entity_observers: EntityHashMap<Entity, ObserverMap>,
}
/// Metadata for observers. Stores a cache mapping trigger ids to the registered observers.
#[derive(Default, Debug)]
pub struct Observers {
// Cached ECS observers to save a lookup most common triggers.
on_add: CachedObservers,
on_insert: CachedObservers,
on_remove: CachedObservers,
// Map from trigger type to set of observers
cache: HashMap<ComponentId, CachedObservers>,
}
impl Observers {
pub(crate) fn get_observers(&mut self, event_type: ComponentId) -> &mut CachedObservers {
match event_type {
ON_ADD => &mut self.on_add,
ON_INSERT => &mut self.on_insert,
ON_REMOVE => &mut self.on_remove,
_ => self.cache.entry(event_type).or_default(),
}
}
pub(crate) fn try_get_observers(&self, event_type: ComponentId) -> Option<&CachedObservers> {
match event_type {
ON_ADD => Some(&self.on_add),
ON_INSERT => Some(&self.on_insert),
ON_REMOVE => Some(&self.on_remove),
_ => self.cache.get(&event_type),
}
}
/// This will run the observers of the given `event_type`, targeting the given `entity` and `components`.
pub(crate) fn invoke<T>(
mut world: DeferredWorld,
event_type: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
data: &mut T,
) {
// SAFETY: You cannot get a mutable reference to `observers` from `DeferredWorld`
let (mut world, observers) = unsafe {
let world = world.as_unsafe_world_cell();
// SAFETY: There are no outstanding world references
world.increment_trigger_id();
let observers = world.observers();
let Some(observers) = observers.try_get_observers(event_type) else {
return;
};
// SAFETY: The only outstanding reference to world is `observers`
(world.into_deferred(), observers)
};
let mut trigger_observer = |(&observer, runner): (&Entity, &ObserverRunner)| {
(runner)(
world.reborrow(),
ObserverTrigger {
observer,
event_type,
entity,
},
data.into(),
);
};
// Trigger observers listening for any kind of this trigger
observers.map.iter().for_each(&mut trigger_observer);
// Trigger entity observers listening for this kind of trigger
if entity != Entity::PLACEHOLDER {
if let Some(map) = observers.entity_observers.get(&entity) {
map.iter().for_each(&mut trigger_observer);
}
}
// Trigger observers listening to this trigger targeting a specific component
components.for_each(|id| {
if let Some(component_observers) = observers.component_observers.get(&id) {
component_observers
.map
.iter()
.for_each(&mut trigger_observer);
if entity != Entity::PLACEHOLDER {
if let Some(map) = component_observers.entity_map.get(&entity) {
map.iter().for_each(&mut trigger_observer);
}
}
}
});
}
pub(crate) fn is_archetype_cached(event_type: ComponentId) -> Option<ArchetypeFlags> {
match event_type {
ON_ADD => Some(ArchetypeFlags::ON_ADD_OBSERVER),
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
_ => None,
}
}
pub(crate) fn update_archetype_flags(
&self,
component_id: ComponentId,
flags: &mut ArchetypeFlags,
) {
if self.on_add.component_observers.contains_key(&component_id) {
flags.insert(ArchetypeFlags::ON_ADD_OBSERVER);
}
if self
.on_insert
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_INSERT_OBSERVER);
}
if self
.on_remove
.component_observers
.contains_key(&component_id)
{
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
}
}
}
impl World {
/// Spawn a "global" [`Observer`] and returns it's [`Entity`].
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> EntityWorldMut {
self.spawn(Observer::new(system))
}
/// Triggers the given `event`, which will run any observers watching for it.
pub fn trigger(&mut self, event: impl Event) {
TriggerEvent { event, targets: () }.apply(self);
}
/// Triggers the given `event` for the given `targets`, which will run any observers watching for it.
pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) {
TriggerEvent { event, targets }.apply(self);
}
/// Register an observer to the cache, called when an observer is created
pub(crate) fn register_observer(&mut self, observer_entity: Entity) {
// SAFETY: References do not alias.
let (observer_state, archetypes, observers) = unsafe {
let observer_state: *const ObserverState =
self.get::<ObserverState>(observer_entity).unwrap();
// Populate ObservedBy for each observed entity.
for watched_entity in &(*observer_state).descriptor.entities {
let mut entity_mut = self.entity_mut(*watched_entity);
let mut observed_by = entity_mut.entry::<ObservedBy>().or_default();
observed_by.0.push(observer_entity);
}
(&*observer_state, &mut self.archetypes, &mut self.observers)
};
let descriptor = &observer_state.descriptor;
for &event_type in &descriptor.events {
let cache = observers.get_observers(event_type);
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
cache.map.insert(observer_entity, observer_state.runner);
} else if descriptor.components.is_empty() {
// Observer is not targeting any components so register it as an entity observer
for &watched_entity in &observer_state.descriptor.entities {
let map = cache.entity_observers.entry(watched_entity).or_default();
map.insert(observer_entity, observer_state.runner);
}
} else {
// Register observer for each watched component
for &component in &descriptor.components {
let observers =
cache
.component_observers
.entry(component)
.or_insert_with(|| {
if let Some(flag) = Observers::is_archetype_cached(event_type) {
archetypes.update_flags(component, flag, true);
}
CachedComponentObservers::default()
});
if descriptor.entities.is_empty() {
// Register for all triggers targeting the component
observers.map.insert(observer_entity, observer_state.runner);
} else {
// Register for each watched entity
for &watched_entity in &descriptor.entities {
let map = observers.entity_map.entry(watched_entity).or_default();
map.insert(observer_entity, observer_state.runner);
}
}
}
}
}
}
/// Remove the observer from the cache, called when an observer gets despawned
pub(crate) fn unregister_observer(&mut self, entity: Entity, descriptor: ObserverDescriptor) {
let archetypes = &mut self.archetypes;
let observers = &mut self.observers;
for &event_type in &descriptor.events {
let cache = observers.get_observers(event_type);
if descriptor.components.is_empty() && descriptor.entities.is_empty() {
cache.map.remove(&entity);
} else if descriptor.components.is_empty() {
for watched_entity in &descriptor.entities {
// This check should be unnecessary since this observer hasn't been unregistered yet
let Some(observers) = cache.entity_observers.get_mut(watched_entity) else {
continue;
};
observers.remove(&entity);
if observers.is_empty() {
cache.entity_observers.remove(watched_entity);
}
}
} else {
for component in &descriptor.components {
let Some(observers) = cache.component_observers.get_mut(component) else {
continue;
};
if descriptor.entities.is_empty() {
observers.map.remove(&entity);
} else {
for watched_entity in &descriptor.entities {
let Some(map) = observers.entity_map.get_mut(watched_entity) else {
continue;
};
map.remove(&entity);
if map.is_empty() {
observers.entity_map.remove(watched_entity);
}
}
}
if observers.map.is_empty() && observers.entity_map.is_empty() {
cache.component_observers.remove(component);
if let Some(flag) = Observers::is_archetype_cached(event_type) {
archetypes.update_flags(*component, flag, false);
}
}
}
}
}
}
}
#[cfg(test)]
mod tests {
use bevy_ptr::OwningPtr;
use crate as bevy_ecs;
use crate::observer::{EmitDynamicTrigger, Observer, ObserverDescriptor, ObserverState};
use crate::prelude::*;
#[derive(Component)]
struct A;
#[derive(Component)]
struct B;
#[derive(Component)]
struct C;
#[derive(Component)]
#[component(storage = "SparseSet")]
struct S;
#[derive(Event)]
struct EventA;
#[derive(Resource, Default)]
struct R(usize);
impl R {
#[track_caller]
fn assert_order(&mut self, count: usize) {
assert_eq!(count, self.0);
self.0 += 1;
}
}
#[test]
fn observer_order_spawn_despawn() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_insert_remove() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, A>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, A>, mut res: ResMut<R>| res.assert_order(2));
let mut entity = world.spawn_empty();
entity.insert(A);
entity.remove::<A>();
entity.flush();
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_insert_remove_sparse() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, S>, mut res: ResMut<R>| res.assert_order(0));
world.observe(|_: Trigger<OnInsert, S>, mut res: ResMut<R>| res.assert_order(1));
world.observe(|_: Trigger<OnRemove, S>, mut res: ResMut<R>| res.assert_order(2));
let mut entity = world.spawn_empty();
entity.insert(S);
entity.remove::<S>();
entity.flush();
assert_eq!(3, world.resource::<R>().0);
}
#[test]
fn observer_order_recursive() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(
|obs: Trigger<OnAdd, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(0);
commands.entity(obs.entity()).insert(B);
},
);
world.observe(
|obs: Trigger<OnRemove, A>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(2);
commands.entity(obs.entity()).remove::<B>();
},
);
world.observe(
|obs: Trigger<OnAdd, B>, mut res: ResMut<R>, mut commands: Commands| {
res.assert_order(1);
commands.entity(obs.entity()).remove::<A>();
},
);
world.observe(|_: Trigger<OnRemove, B>, mut res: ResMut<R>| {
res.assert_order(3);
});
let entity = world.spawn(A).flush();
let entity = world.get_entity(entity).unwrap();
assert!(!entity.contains::<A>());
assert!(!entity.contains::<B>());
assert_eq!(4, world.resource::<R>().0);
}
#[test]
fn observer_multiple_listeners() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.observe(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1);
world.spawn(A).flush();
assert_eq!(2, world.resource::<R>().0);
// Our A entity plus our two observers
assert_eq!(world.entities().len(), 3);
}
#[test]
fn observer_multiple_events() {
let mut world = World::new();
world.init_resource::<R>();
let on_remove = world.init_component::<OnRemove>();
world.spawn(
Observer::new(|_: Trigger<OnAdd, A>, mut res: ResMut<R>| res.0 += 1)
.with_event(on_remove),
);
let entity = world.spawn(A).id();
world.despawn(entity);
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_multiple_components() {
let mut world = World::new();
world.init_resource::<R>();
world.init_component::<A>();
world.init_component::<B>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
let entity = world.spawn(A).id();
world.entity_mut(entity).insert(B);
world.flush();
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_despawn() {
let mut world = World::new();
world.init_resource::<R>();
let observer = world
.observe(|_: Trigger<OnAdd, A>| panic!("Observer triggered after being despawned."))
.id();
world.despawn(observer);
world.spawn(A).flush();
}
#[test]
fn observer_multiple_matches() {
let mut world = World::new();
world.init_resource::<R>();
world.observe(|_: Trigger<OnAdd, (A, B)>, mut res: ResMut<R>| res.0 += 1);
world.spawn((A, B)).flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_no_target() {
let mut world = World::new();
world.init_resource::<R>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
assert_eq!(obs.entity(), Entity::PLACEHOLDER);
res.0 += 1;
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush.
world.flush();
world.trigger(EventA);
world.flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_entity_routing() {
let mut world = World::new();
world.init_resource::<R>();
world
.spawn_empty()
.observe(|_: Trigger<EventA>| panic!("Trigger routed to non-targeted entity."));
let entity = world
.spawn_empty()
.observe(|_: Trigger<EventA>, mut res: ResMut<R>| res.0 += 1)
.id();
world.observe(move |obs: Trigger<EventA>, mut res: ResMut<R>| {
assert_eq!(obs.entity(), entity);
res.0 += 1;
});
// TODO: ideally this flush is not necessary, but right now observe() returns WorldEntityMut
// and therefore does not automatically flush.
world.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(2, world.resource::<R>().0);
}
#[test]
fn observer_dynamic_component() {
let mut world = World::new();
world.init_resource::<R>();
let component_id = world.init_component::<A>();
world.spawn(
Observer::new(|_: Trigger<OnAdd>, mut res: ResMut<R>| res.0 += 1)
.with_component(component_id),
);
let mut entity = world.spawn_empty();
OwningPtr::make(A, |ptr| {
// SAFETY: we registered `component_id` above.
unsafe { entity.insert_by_id(component_id, ptr) };
});
let entity = entity.flush();
world.trigger_targets(EventA, entity);
world.flush();
assert_eq!(1, world.resource::<R>().0);
}
#[test]
fn observer_dynamic_trigger() {
let mut world = World::new();
world.init_resource::<R>();
let event_a = world.init_component::<EventA>();
world.spawn(ObserverState {
descriptor: ObserverDescriptor::default().with_triggers(vec![event_a]),
runner: |mut world, _trigger, _ptr| {
world.resource_mut::<R>().0 += 1;
},
..Default::default()
});
world.commands().add(
// SAFETY: we registered `trigger` above and it matches the type of TriggerA
unsafe { EmitDynamicTrigger::new_with_id(event_a, EventA, ()) },
);
world.flush();
assert_eq!(1, world.resource::<R>().0);
}
}

View file

@ -0,0 +1,409 @@
use crate::{
component::{ComponentHooks, ComponentId, StorageType},
observer::{ObserverDescriptor, ObserverTrigger},
prelude::*,
query::DebugCheckedUnwrap,
system::{IntoObserverSystem, ObserverSystem},
world::DeferredWorld,
};
use bevy_ptr::PtrMut;
/// Contains [`Observer`] information. This defines how a given observer behaves. It is the
/// "source of truth" for a given observer entity's behavior.
pub struct ObserverState {
pub(crate) descriptor: ObserverDescriptor,
pub(crate) runner: ObserverRunner,
pub(crate) last_trigger_id: u32,
pub(crate) despawned_watched_entities: u32,
}
impl Default for ObserverState {
fn default() -> Self {
Self {
runner: |_, _, _| {},
last_trigger_id: 0,
despawned_watched_entities: 0,
descriptor: Default::default(),
}
}
}
impl ObserverState {
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
pub fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
/// Observe the given event list. This will cause the [`Observer`] to run whenever an event with any of the given [`ComponentId`]s
/// is triggered.
pub fn with_events(mut self, events: impl IntoIterator<Item = ComponentId>) -> Self {
self.descriptor.events.extend(events);
self
}
/// Observe the given [`Entity`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for any [`Entity`] target in the list.
pub fn with_entities(mut self, entities: impl IntoIterator<Item = Entity>) -> Self {
self.descriptor.entities.extend(entities);
self
}
/// Observe the given [`ComponentId`] list. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for any [`ComponentId`] target in the list.
pub fn with_components(mut self, components: impl IntoIterator<Item = ComponentId>) -> Self {
self.descriptor.components.extend(components);
self
}
}
impl Component for ObserverState {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|mut world, entity, _| {
world.commands().add(move |world: &mut World| {
world.register_observer(entity);
});
});
hooks.on_remove(|mut world, entity, _| {
let descriptor = std::mem::take(
&mut world
.entity_mut(entity)
.get_mut::<ObserverState>()
.unwrap()
.as_mut()
.descriptor,
);
world.commands().add(move |world: &mut World| {
world.unregister_observer(entity, descriptor);
});
});
}
}
/// Type for function that is run when an observer is triggered.
/// Typically refers to the default runner that runs the system stored in the associated [`ObserverSystemComponent`],
/// but can be overridden for custom behaviour.
pub type ObserverRunner = fn(DeferredWorld, ObserverTrigger, PtrMut);
/// An [`Observer`] system. Add this [`Component`] to an [`Entity`] to turn it into an "observer".
///
/// Observers listen for a "trigger" of a specific [`Event`]. Events are triggered by calling [`World::trigger`] or [`World::trigger_targets`].
///
/// Note that "buffered" events sent using [`EventReader`] and [`EventWriter`] are _not_ automatically triggered. They must be triggered at a specific
/// point in the schedule.
///
/// # Usage
///
/// The simplest usage
/// of the observer pattern looks like this:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// #[derive(Event)]
/// struct Speak {
/// message: String,
/// }
///
/// world.observe(|trigger: Trigger<Speak>| {
/// println!("{}", trigger.event().message);
/// });
///
/// // Observers currently require a flush() to be registered. In the context of schedules,
/// // this will generally be done for you.
/// world.flush();
///
/// world.trigger(Speak {
/// message: "Hello!".into(),
/// });
/// ```
///
/// Notice that we used [`World::observe`]. This is just a shorthand for spawning an [`Observer`] manually:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct Speak;
/// // These are functionally the same:
/// world.observe(|trigger: Trigger<Speak>| {});
/// world.spawn(Observer::new(|trigger: Trigger<Speak>| {}));
/// ```
///
/// Observers are systems. They can access arbitrary [`World`] data by adding [`SystemParam`]s:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct PrintNames;
/// # #[derive(Component, Debug)]
/// # struct Name;
/// world.observe(|trigger: Trigger<PrintNames>, names: Query<&Name>| {
/// for name in &names {
/// println!("{name:?}");
/// }
/// });
/// ```
///
/// Note that [`Trigger`] must always be the first parameter.
///
/// You can also add [`Commands`], which means you can spawn new entities, insert new components, etc:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct SpawnThing;
/// # #[derive(Component, Debug)]
/// # struct Thing;
/// world.observe(|trigger: Trigger<SpawnThing>, mut commands: Commands| {
/// commands.spawn(Thing);
/// });
/// ```
///
/// Observers can also trigger new events:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # #[derive(Event)]
/// # struct A;
/// # #[derive(Event)]
/// # struct B;
/// world.observe(|trigger: Trigger<A>, mut commands: Commands| {
/// commands.trigger(B);
/// });
/// ```
///
/// When the commands are flushed (including these "nested triggers") they will be
/// recursively evaluated until there are no commands left, meaning nested triggers all
/// evaluate at the same time!
///
/// Events can be triggered for entities, which will be passed to the [`Observer`]:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// #[derive(Event)]
/// struct Explode;
///
/// world.observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("Entity {:?} goes BOOM!", trigger.entity());
/// commands.entity(trigger.entity()).despawn();
/// });
///
/// world.flush();
///
/// world.trigger_targets(Explode, entity);
/// ```
///
/// You can trigger multiple entities at once:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event)]
/// # struct Explode;
/// world.trigger_targets(Explode, [e1, e2]);
/// ```
///
/// Observers can also watch _specific_ entities, which enables you to assign entity-specific logic:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Component, Debug)]
/// # struct Name(String);
/// # let mut world = World::default();
/// # let e1 = world.spawn_empty().id();
/// # let e2 = world.spawn_empty().id();
/// # #[derive(Event)]
/// # struct Explode;
/// world.entity_mut(e1).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("Boom!");
/// commands.entity(trigger.entity()).despawn();
/// });
///
/// world.entity_mut(e2).observe(|trigger: Trigger<Explode>, mut commands: Commands| {
/// println!("The explosion fizzles! This entity is immune!");
/// });
/// ```
///
/// If all entities watched by a given [`Observer`] are despawned, the [`Observer`] entity will also be despawned.
/// This protects against observer "garbage" building up over time.
///
/// The examples above calling [`EntityWorldMut::observe`] to add entity-specific observer logic are (once again)
/// just shorthand for spawning an [`Observer`] directly:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # let mut world = World::default();
/// # let entity = world.spawn_empty().id();
/// # #[derive(Event)]
/// # struct Explode;
/// let mut observer = Observer::new(|trigger: Trigger<Explode>| {});
/// observer.watch_entity(entity);
/// world.spawn(observer);
/// ```
///
/// Note that the [`Observer`] component is not added to the entity it is observing. Observers should always be their own entities!
///
/// You can call [`Observer::watch_entity`] more than once, which allows you to watch multiple entities with the same [`Observer`].
///
/// When first added, [`Observer`] will also create an [`ObserverState`] component, which registers the observer with the [`World`] and
/// serves as the "source of truth" of the observer.
///
/// [`SystemParam`]: crate::system::SystemParam
pub struct Observer<T: 'static, B: Bundle> {
system: BoxedObserverSystem<T, B>,
descriptor: ObserverDescriptor,
}
impl<E: Event, B: Bundle> Observer<E, B> {
/// Creates a new [`Observer`], which defaults to a "global" observer. This means it will run whenever the event `E` is triggered
/// for _any_ entity (or no entity).
pub fn new<M>(system: impl IntoObserverSystem<E, B, M>) -> Self {
Self {
system: Box::new(IntoObserverSystem::into_system(system)),
descriptor: Default::default(),
}
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
pub fn with_entity(mut self, entity: Entity) -> Self {
self.descriptor.entities.push(entity);
self
}
/// Observe the given `entity`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// for the `entity`.
/// Note that if this is called _after_ an [`Observer`] is spawned, it will produce no effects.
pub fn watch_entity(&mut self, entity: Entity) {
self.descriptor.entities.push(entity);
}
/// Observe the given `component`. This will cause the [`Observer`] to run whenever the [`Event`] is triggered
/// with the given component target.
pub fn with_component(mut self, component: ComponentId) -> Self {
self.descriptor.components.push(component);
self
}
/// Observe the given `event`. This will cause the [`Observer`] to run whenever an event with the given [`ComponentId`]
/// is triggered.
pub fn with_event(mut self, event: ComponentId) -> Self {
self.descriptor.events.push(event);
self
}
}
impl<E: Event, B: Bundle> Component for Observer<E, B> {
const STORAGE_TYPE: StorageType = StorageType::SparseSet;
fn register_component_hooks(hooks: &mut ComponentHooks) {
hooks.on_add(|mut world, entity, _| {
world.commands().add(move |world: &mut World| {
let event_type = world.init_component::<E>();
let mut components = Vec::new();
B::component_ids(&mut world.components, &mut world.storages, &mut |id| {
components.push(id);
});
let mut descriptor = ObserverDescriptor {
events: vec![event_type],
components,
..Default::default()
};
// Initialize System
let system: *mut dyn ObserverSystem<E, B> =
if let Some(mut observe) = world.get_mut::<Self>(entity) {
descriptor.merge(&observe.descriptor);
&mut *observe.system
} else {
return;
};
// SAFETY: World reference is exclusive and initialize does not touch system, so references do not alias
unsafe {
(*system).initialize(world);
}
{
let mut entity = world.entity_mut(entity);
if let crate::world::Entry::Vacant(entry) = entity.entry::<ObserverState>() {
entry.insert(ObserverState {
descriptor,
runner: observer_system_runner::<E, B>,
..Default::default()
});
}
}
});
});
}
}
/// Equivalent to [`BoxedSystem`](crate::system::BoxedSystem) for [`ObserverSystem`].
pub type BoxedObserverSystem<E = (), B = ()> = Box<dyn ObserverSystem<E, B>>;
fn observer_system_runner<E: Event, B: Bundle>(
mut world: DeferredWorld,
observer_trigger: ObserverTrigger,
ptr: PtrMut,
) {
let world = world.as_unsafe_world_cell();
// SAFETY: Observer was triggered so must still exist in world
let observer_cell = unsafe {
world
.get_entity(observer_trigger.observer)
.debug_checked_unwrap()
};
// SAFETY: Observer was triggered so must have an `ObserverState`
let mut state = unsafe {
observer_cell
.get_mut::<ObserverState>()
.debug_checked_unwrap()
};
// TODO: Move this check into the observer cache to avoid dynamic dispatch
// SAFETY: We only access world metadata
let last_trigger = unsafe { world.world_metadata() }.last_trigger_id();
if state.last_trigger_id == last_trigger {
return;
}
state.last_trigger_id = last_trigger;
// SAFETY: Caller ensures `ptr` is castable to `&mut T`
let trigger: Trigger<E, B> = Trigger::new(unsafe { ptr.deref_mut() }, observer_trigger);
// SAFETY: the static lifetime is encapsulated in Trigger / cannot leak out.
// Additionally, IntoObserverSystem is only implemented for functions starting
// with for<'a> Trigger<'a>, meaning users cannot specify Trigger<'static> manually,
// allowing the Trigger<'static> to be moved outside of the context of the system.
// This transmute is obviously not ideal, but it is safe. Ideally we can remove the
// static constraint from ObserverSystem, but so far we have not found a way.
let trigger: Trigger<'static, E, B> = unsafe { std::mem::transmute(trigger) };
// SAFETY: Observer was triggered so must have an `ObserverSystemComponent`
let system = unsafe {
&mut observer_cell
.get_mut::<Observer<E, B>>()
.debug_checked_unwrap()
.system
};
system.update_archetype_component_access(world);
// SAFETY:
// - `update_archetype_component_access` was just called
// - there are no outstanding references to world except a private component
// - system is an `ObserverSystem` so won't mutate world beyond the access of a `DeferredWorld`
// - system is the same type erased system from above
unsafe {
system.run_unsafe(trigger, world);
system.queue_deferred(world.into_deferred());
}
}

View file

@ -0,0 +1,165 @@
use crate::{
component::ComponentId,
entity::Entity,
event::Event,
world::{Command, DeferredWorld, World},
};
/// A [`Command`] that emits a given trigger for a given set of targets.
pub struct TriggerEvent<E, Targets: TriggerTargets = ()> {
/// The event to trigger.
pub event: E,
/// The targets to trigger the event for.
pub targets: Targets,
}
impl<E: Event, Targets: TriggerTargets> Command for TriggerEvent<E, Targets> {
fn apply(mut self, world: &mut World) {
let event_type = world.init_component::<E>();
trigger_event(world, event_type, &mut self.event, self.targets);
}
}
/// Emit a trigger for a dynamic component id. This is unsafe and must be verified manually.
pub struct EmitDynamicTrigger<T, Targets: TriggerTargets = ()> {
event_type: ComponentId,
event_data: T,
targets: Targets,
}
impl<E, Targets: TriggerTargets> EmitDynamicTrigger<E, Targets> {
/// Sets the event type of the resulting trigger, used for dynamic triggers
/// # Safety
/// Caller must ensure that the component associated with `event_type` is accessible as E
pub unsafe fn new_with_id(event_type: ComponentId, event_data: E, targets: Targets) -> Self {
Self {
event_type,
event_data,
targets,
}
}
}
impl<E: Event, Targets: TriggerTargets> Command for EmitDynamicTrigger<E, Targets> {
fn apply(mut self, world: &mut World) {
trigger_event(world, self.event_type, &mut self.event_data, self.targets);
}
}
#[inline]
fn trigger_event<E, Targets: TriggerTargets>(
world: &mut World,
event_type: ComponentId,
event_data: &mut E,
targets: Targets,
) {
let mut world = DeferredWorld::from(world);
if targets.entities().len() == 0 {
// SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new`
unsafe {
world.trigger_observers_with_data(
event_type,
Entity::PLACEHOLDER,
targets.components(),
event_data,
);
};
} else {
for target in targets.entities() {
// SAFETY: T is accessible as the type represented by self.trigger, ensured in `Self::new`
unsafe {
world.trigger_observers_with_data(
event_type,
target,
targets.components(),
event_data,
);
};
}
}
}
/// Represents a collection of targets for a specific [`Trigger`] of an [`Event`]. Targets can be of type [`Entity`] or [`ComponentId`].
/// When a trigger occurs for a given event and [`TriggerTargets`], any [`Observer`] that watches for that specific event-target combination
/// will run.
///
/// [`Trigger`]: crate::observer::Trigger
/// [`Observer`]: crate::observer::Observer
pub trait TriggerTargets: Send + Sync + 'static {
/// The components the trigger should target.
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId>;
/// The entities the trigger should target.
fn entities(&self) -> impl ExactSizeIterator<Item = Entity>;
}
impl TriggerTargets for () {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl TriggerTargets for Entity {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
std::iter::once(*self)
}
}
impl TriggerTargets for Vec<Entity> {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
self.iter().copied()
}
}
impl<const N: usize> TriggerTargets for [Entity; N] {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
[].into_iter()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
self.iter().copied()
}
}
impl TriggerTargets for ComponentId {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
std::iter::once(*self)
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl TriggerTargets for Vec<ComponentId> {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
self.iter().copied()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}
impl<const N: usize> TriggerTargets for [ComponentId; N] {
fn components(&self) -> impl ExactSizeIterator<Item = ComponentId> {
self.iter().copied()
}
fn entities(&self) -> impl ExactSizeIterator<Item = Entity> {
[].into_iter()
}
}

View file

@ -316,7 +316,7 @@ impl<T: SparseSetIndex> Access<T> {
/// otherwise would allow for queries to be considered disjoint when they shouldn't:
/// - `Query<(&mut T, Option<&U>)>` read/write `T`, read `U`, with `U`
/// - `Query<&mut T, Without<U>>` read/write `T`, without `U`
/// from this we could reasonably conclude that the queries are disjoint but they aren't.
/// from this we could reasonably conclude that the queries are disjoint but they aren't.
///
/// In order to solve this the actual access that `Query<(&mut T, Option<&U>)>` has
/// is read/write `T`, read `U`. It must still have a read `U` access otherwise the following
@ -647,6 +647,16 @@ impl<T: SparseSetIndex> FilteredAccessSet<T> {
.extend(filtered_access_set.filtered_accesses);
}
/// Marks the set as reading all possible indices of type T.
pub fn read_all(&mut self) {
self.combined_access.read_all();
}
/// Marks the set as writing all T.
pub fn write_all(&mut self) {
self.combined_access.write_all();
}
/// Removes all accesses stored in this set.
pub fn clear(&mut self) {
self.combined_access.clear();

View file

@ -43,9 +43,8 @@ pub struct QueryBuilder<'w, D: QueryData = (), F: QueryFilter = ()> {
impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Creates a new builder with the accesses required for `Q` and `F`
pub fn new(world: &'w mut World) -> Self {
let initializer = &mut world.component_initializer();
let fetch_state = D::init_state(initializer);
let filter_state = F::init_state(initializer);
let fetch_state = D::init_state(world);
let filter_state = F::init_state(world);
let mut access = FilteredAccess::default();
D::update_component_access(&fetch_state, &mut access);
@ -96,7 +95,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Adds accesses required for `T` to self.
pub fn data<T: QueryData>(&mut self) -> &mut Self {
let state = T::init_state(&mut self.world.component_initializer());
let state = T::init_state(self.world);
let mut access = FilteredAccess::default();
T::update_component_access(&state, &mut access);
self.extend_access(access);
@ -105,7 +104,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
/// Adds filter from `T` to self.
pub fn filter<T: QueryFilter>(&mut self) -> &mut Self {
let state = T::init_state(&mut self.world.component_initializer());
let state = T::init_state(self.world);
let mut access = FilteredAccess::default();
T::update_component_access(&state, &mut access);
self.extend_access(access);
@ -223,9 +222,8 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> {
pub fn transmute_filtered<NewD: QueryData, NewF: QueryFilter>(
&mut self,
) -> &mut QueryBuilder<'w, NewD, NewF> {
let initializer = &mut self.world.component_initializer();
let mut fetch_state = NewD::init_state(initializer);
let filter_state = NewF::init_state(initializer);
let mut fetch_state = NewD::init_state(self.world);
let filter_state = NewF::init_state(self.world);
NewD::set_access(&mut fetch_state, &self.access);

View file

@ -1,20 +1,20 @@
use crate::{
archetype::{Archetype, Archetypes},
change_detection::{Ticks, TicksMut},
component::{Component, ComponentId, ComponentInitializer, Components, StorageType, Tick},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{ComponentSparseSet, Table, TableRow},
world::{
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityRef, FilteredEntityMut,
FilteredEntityRef, Mut, Ref,
FilteredEntityRef, Mut, Ref, World,
},
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
use std::{cell::UnsafeCell, marker::PhantomData};
/// Types that can be fetched from a [`World`](crate::world::World) using a [`Query`].
/// Types that can be fetched from a [`World`] using a [`Query`].
///
/// There are many types that natively implement this trait:
///
@ -335,7 +335,7 @@ unsafe impl WorldQuery for Entity {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -407,7 +407,7 @@ unsafe impl WorldQuery for EntityLocation {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -486,7 +486,7 @@ unsafe impl<'a> WorldQuery for EntityRef<'a> {
access.read_all();
}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -562,7 +562,7 @@ unsafe impl<'a> WorldQuery for EntityMut<'a> {
access.write_all();
}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -660,7 +660,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityRef<'a> {
filtered_access.access.extend(&state.access);
}
fn init_state(_initializer: &mut ComponentInitializer) -> Self::State {
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
}
@ -772,7 +772,7 @@ unsafe impl<'a> WorldQuery for FilteredEntityMut<'a> {
filtered_access.access.extend(&state.access);
}
fn init_state(_initializer: &mut ComponentInitializer) -> Self::State {
fn init_state(_world: &mut World) -> Self::State {
FilteredAccess::default()
}
@ -846,7 +846,7 @@ unsafe impl WorldQuery for &Archetype {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) {}
fn init_state(_world: &mut World) {}
fn get_state(_components: &Components) -> Option<()> {
Some(())
@ -995,8 +995,8 @@ unsafe impl<T: Component> WorldQuery for &T {
access.add_read(component_id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1178,8 +1178,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
access.add_read(component_id);
}
fn init_state(initializer: &mut ComponentInitializer<'_>) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1361,8 +1361,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
access.add_write(component_id);
}
fn init_state(initializer: &mut ComponentInitializer<'_>) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1460,8 +1460,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Mut<'__w, T> {
}
// Forwarded to `&mut T`
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
<&mut T as WorldQuery>::init_state(initializer)
fn init_state(world: &mut World) -> ComponentId {
<&mut T as WorldQuery>::init_state(world)
}
// Forwarded to `&mut T`
@ -1581,8 +1581,8 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
access.extend_access(&intermediate);
}
fn init_state(initializer: &mut ComponentInitializer) -> T::State {
T::init_state(initializer)
fn init_state(world: &mut World) -> T::State {
T::init_state(world)
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1736,8 +1736,8 @@ unsafe impl<T: Component> WorldQuery for Has<T> {
access.access_mut().add_archetypal(component_id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -1882,8 +1882,8 @@ macro_rules! impl_anytuple_fetch {
*_access = _new_access;
}
#[allow(unused_variables)]
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
($($name::init_state(initializer),)*)
fn init_state(world: &mut World) -> Self::State {
($($name::init_state(world),)*)
}
#[allow(unused_variables)]
fn get_state(components: &Components) -> Option<Self::State> {
@ -1959,8 +1959,8 @@ unsafe impl<D: QueryData> WorldQuery for NopWorldQuery<D> {
fn update_component_access(_state: &D::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
D::init_state(initializer)
fn init_state(world: &mut World) -> Self::State {
D::init_state(world)
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -2026,7 +2026,7 @@ unsafe impl<T: ?Sized> WorldQuery for PhantomData<T> {
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {}
fn init_state(_initializer: &mut ComponentInitializer) -> Self::State {}
fn init_state(_world: &mut World) -> Self::State {}
fn get_state(_components: &Components) -> Option<Self::State> {
Some(())

View file

@ -1,10 +1,10 @@
use crate::{
archetype::Archetype,
component::{Component, ComponentId, ComponentInitializer, Components, StorageType, Tick},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::Entity,
query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{Column, ComponentSparseSet, Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
use bevy_utils::all_tuples;
@ -183,8 +183,8 @@ unsafe impl<T: Component> WorldQuery for With<T> {
access.and_with(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -291,8 +291,8 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
access.and_without(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -461,8 +461,8 @@ macro_rules! impl_or_query_filter {
*access = _new_access;
}
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
($($filter::init_state(initializer),)*)
fn init_state(world: &mut World) -> Self::State {
($($filter::init_state(world),)*)
}
fn get_state(components: &Components) -> Option<Self::State> {
@ -693,8 +693,8 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
access.add_read(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<ComponentId> {
@ -904,8 +904,8 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
access.add_read(id);
}
fn init_state(initializer: &mut ComponentInitializer) -> ComponentId {
initializer.init_component::<T>()
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn get_state(components: &Components) -> Option<ComponentId> {

View file

@ -44,9 +44,9 @@ pub(super) union StorageId {
///
/// This data is cached between system runs, and is used to:
/// - store metadata about which [`Table`] or [`Archetype`] are matched by the query. "Matched" means
/// that the query will iterate over the data in the matched table/archetype.
/// that the query will iterate over the data in the matched table/archetype.
/// - cache the [`State`] needed to compute the [`Fetch`] struct used to retrieve data
/// from a specific [`Table`] or [`Archetype`]
/// from a specific [`Table`] or [`Archetype`]
/// - build iterators that can iterate over the query results
///
/// [`State`]: crate::query::world_query::WorldQuery::State
@ -178,9 +178,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// `new_archetype` and its variants must be called on all of the World's archetypes before the
/// state can return valid query results.
fn new_uninitialized(world: &mut World) -> Self {
let initializer = &mut world.component_initializer();
let fetch_state = D::init_state(initializer);
let filter_state = F::init_state(initializer);
let fetch_state = D::init_state(world);
let filter_state = F::init_state(world);
let mut component_access = FilteredAccess::default();
D::update_component_access(&fetch_state, &mut component_access);
@ -215,9 +214,8 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// Creates a new [`QueryState`] from a given [`QueryBuilder`] and inherits its [`FilteredAccess`].
pub fn from_builder(builder: &mut QueryBuilder<D, F>) -> Self {
let initializer = &mut builder.world_mut().component_initializer();
let mut fetch_state = D::init_state(initializer);
let filter_state = F::init_state(initializer);
let mut fetch_state = D::init_state(builder.world_mut());
let filter_state = F::init_state(builder.world_mut());
D::set_access(&mut fetch_state, builder.access());
let mut state = Self {
@ -903,9 +901,9 @@ impl<D: QueryData, F: QueryFilter> QueryState<D, F> {
/// # Safety
///
/// * `world` must have permission to read all of the components returned from this call.
/// No mutable references may coexist with any of the returned references.
/// No mutable references may coexist with any of the returned references.
/// * This must be called on the same `World` that the `Query` was generated from:
/// use `QueryState::validate_world` to verify this.
/// use `QueryState::validate_world` to verify this.
pub(crate) unsafe fn get_many_read_only_manual<'w, const N: usize>(
&self,
world: UnsafeWorldCell<'w>,

View file

@ -1,10 +1,10 @@
use crate::{
archetype::Archetype,
component::{ComponentId, ComponentInitializer, Components, Tick},
component::{ComponentId, Components, Tick},
entity::Entity,
query::FilteredAccess,
storage::{Table, TableRow},
world::unsafe_world_cell::UnsafeWorldCell,
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
use bevy_utils::all_tuples;
@ -79,7 +79,7 @@ pub unsafe trait WorldQuery {
///
/// # Safety
///
/// - `archetype` and `tables` must be from the same [`World`](crate::world::World) that [`WorldQuery::init_state`] was called on.
/// - `archetype` and `tables` must be from the same [`World`] that [`WorldQuery::init_state`] was called on.
/// - `table` must correspond to `archetype`.
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_archetype<'w>(
@ -94,7 +94,7 @@ pub unsafe trait WorldQuery {
///
/// # Safety
///
/// - `table` must be from the same [`World`](crate::world::World) that [`WorldQuery::init_state`] was called on.
/// - `table` must be from the same [`World`] that [`WorldQuery::init_state`] was called on.
/// - `state` must be the [`State`](Self::State) that `fetch` was initialized with.
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table);
@ -127,7 +127,7 @@ pub unsafe trait WorldQuery {
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>);
/// Creates and initializes a [`State`](WorldQuery::State) for this [`WorldQuery`] type.
fn init_state(initializer: &mut ComponentInitializer) -> Self::State;
fn init_state(world: &mut World) -> Self::State;
/// Attempts to initialize a [`State`](WorldQuery::State) for this [`WorldQuery`] type using read-only
/// access to [`Components`].
@ -213,8 +213,8 @@ macro_rules! impl_tuple_world_query {
$($name::update_component_access($name, _access);)*
}
#[allow(unused_variables)]
fn init_state(initializer: &mut ComponentInitializer) -> Self::State {
($($name::init_state(initializer),)*)
fn init_state(world: &mut World) -> Self::State {
($($name::init_state(world),)*)
}
#[allow(unused_variables)]
fn get_state(components: &Components) -> Option<Self::State> {

View file

@ -116,11 +116,11 @@ impl RemovedComponentEvents {
///
/// 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)
/// and will need to be manually flushed using [`World::clear_trackers`](World::clear_trackers).
///
/// For users of `bevy` and `bevy_app`, this is automatically done in `bevy_app::App::update`.
/// For the main world, [`World::clear_trackers`](World::clear_trackers) is run after the main schedule is run and after
/// `SubApp`'s have run.
/// 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
///

View file

@ -71,6 +71,57 @@ pub type BoxedCondition<In = ()> = Box<dyn ReadOnlySystem<In = In, Out = bool>>;
/// # app.run(&mut world);
/// # assert!(world.resource::<DidRun>().0);
pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `and` return `true`.
///
/// The returned run condition is short-circuiting, meaning
/// `and` will only be invoked if `self` returns `true`.
///
/// # Examples
///
/// ```should_panic
/// use bevy_ecs::prelude::*;
///
/// #[derive(Resource, PartialEq)]
/// struct R(u32);
///
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # fn my_system() {}
/// app.add_systems(
/// // The `resource_equals` run condition will panic since we don't initialize `R`,
/// // just like if we used `Res<R>` in a system.
/// my_system.run_if(resource_equals(R(0))),
/// );
/// # app.run(&mut world);
/// ```
///
/// Use `.and()` to avoid checking the condition.
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Resource, PartialEq)]
/// # struct R(u32);
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # fn my_system() {}
/// app.add_systems(
/// // `resource_equals` will only get run if the resource `R` exists.
/// my_system.run_if(resource_exists::<R>.and(resource_equals(R(0)))),
/// );
/// # app.run(&mut world);
/// ```
///
/// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`].
///
/// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals
fn and<M, C: Condition<M, In>>(self, and: C) -> And<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(and);
let name = format!("{} && {}", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `and_then` return `true`.
///
@ -115,18 +166,122 @@ pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
/// Note that in this case, it's better to just use the run condition [`resource_exists_and_equals`].
///
/// [`resource_exists_and_equals`]: common_conditions::resource_exists_and_equals
fn and_then<M, C: Condition<M, In>>(self, and_then: C) -> AndThen<Self::System, C::System> {
#[deprecated(
note = "Users should use the `.and(condition)` method in lieu of `.and_then(condition)`"
)]
fn and_then<M, C: Condition<M, In>>(self, and_then: C) -> And<Self::System, C::System> {
self.and(and_then)
}
/// Returns a new run condition that only returns `false`
/// if both this one and the passed `nand` return `true`.
///
/// The returned run condition is short-circuiting, meaning
/// `nand` will only be invoked if `self` returns `true`.
///
/// # Examples
///
/// ```compile_fail
/// use bevy::prelude::*;
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum PlayerState {
/// Alive,
/// Dead,
/// }
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum EnemyState {
/// Alive,
/// Dead,
/// }
///
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # fn game_over_credits() {}
/// app.add_systems(
/// // The game_over_credits system will only execute if either the `in_state(PlayerState::Alive)`
/// // run condition or `in_state(EnemyState::Alive)` run condition evaluates to `false`.
/// game_over_credits.run_if(
/// in_state(PlayerState::Alive).nand(in_state(EnemyState::Alive))
/// ),
/// );
/// # app.run(&mut world);
/// ```
///
/// Equivalent logic can be achieved by using `not` in concert with `and`:
///
/// ```compile_fail
/// app.add_systems(
/// game_over_credits.run_if(
/// not(in_state(PlayerState::Alive).and(in_state(EnemyState::Alive)))
/// ),
/// );
/// ```
fn nand<M, C: Condition<M, In>>(self, nand: C) -> Nand<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(and_then);
let name = format!("{} && {}", a.name(), b.name());
let b = IntoSystem::into_system(nand);
let name = format!("!({} && {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
/// Returns a new run condition that only returns `true`
/// if both this one and the passed `nor` return `false`.
///
/// The returned run condition is short-circuiting, meaning
/// `nor` will only be invoked if `self` returns `false`.
///
/// # Examples
///
/// ```compile_fail
/// use bevy::prelude::*;
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum WeatherState {
/// Sunny,
/// Cloudy,
/// }
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum SoilState {
/// Fertilized,
/// NotFertilized,
/// }
///
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # fn slow_plant_growth() {}
/// app.add_systems(
/// // The slow_plant_growth system will only execute if both the `in_state(WeatherState::Sunny)`
/// // run condition and `in_state(SoilState::Fertilized)` run condition evaluate to `false`.
/// slow_plant_growth.run_if(
/// in_state(WeatherState::Sunny).nor(in_state(SoilState::Fertilized))
/// ),
/// );
/// # app.run(&mut world);
/// ```
///
/// Equivalent logic can be achieved by using `not` in concert with `or`:
///
/// ```compile_fail
/// app.add_systems(
/// slow_plant_growth.run_if(
/// not(in_state(WeatherState::Sunny).or(in_state(SoilState::Fertilized)))
/// ),
/// );
/// ```
fn nor<M, C: Condition<M, In>>(self, nor: C) -> Nor<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(nor);
let name = format!("!({} || {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
/// Returns a new run condition that returns `true`
/// if either this one or the passed `or_else` return `true`.
/// if either this one or the passed `or` return `true`.
///
/// The returned run condition is short-circuiting, meaning
/// `or_else` will only be invoked if `self` returns `false`.
/// `or` will only be invoked if `self` returns `false`.
///
/// # Examples
///
@ -145,7 +300,7 @@ pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
/// # fn my_system(mut c: ResMut<C>) { c.0 = true; }
/// app.add_systems(
/// // Only run the system if either `A` or `B` exist.
/// my_system.run_if(resource_exists::<A>.or_else(resource_exists::<B>)),
/// my_system.run_if(resource_exists::<A>.or(resource_exists::<B>)),
/// );
/// #
/// # world.insert_resource(C(false));
@ -162,12 +317,153 @@ pub trait Condition<Marker, In = ()>: sealed::Condition<Marker, In> {
/// # app.run(&mut world);
/// # assert!(world.resource::<C>().0);
/// ```
fn or_else<M, C: Condition<M, In>>(self, or_else: C) -> OrElse<Self::System, C::System> {
fn or<M, C: Condition<M, In>>(self, or: C) -> Or<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(or_else);
let b = IntoSystem::into_system(or);
let name = format!("{} || {}", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
/// Returns a new run condition that returns `true`
/// if either this one or the passed `or` return `true`.
///
/// The returned run condition is short-circuiting, meaning
/// `or` will only be invoked if `self` returns `false`.
///
/// # Examples
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// #[derive(Resource, PartialEq)]
/// struct A(u32);
///
/// #[derive(Resource, PartialEq)]
/// struct B(u32);
///
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # #[derive(Resource)] struct C(bool);
/// # fn my_system(mut c: ResMut<C>) { c.0 = true; }
/// app.add_systems(
/// // Only run the system if either `A` or `B` exist.
/// my_system.run_if(resource_exists::<A>.or(resource_exists::<B>)),
/// );
/// #
/// # world.insert_resource(C(false));
/// # app.run(&mut world);
/// # assert!(!world.resource::<C>().0);
/// #
/// # world.insert_resource(A(0));
/// # app.run(&mut world);
/// # assert!(world.resource::<C>().0);
/// #
/// # world.remove_resource::<A>();
/// # world.insert_resource(B(0));
/// # world.insert_resource(C(false));
/// # app.run(&mut world);
/// # assert!(world.resource::<C>().0);
/// ```
#[deprecated(
note = "Users should use the `.or(condition)` method in lieu of `.or_else(condition)`"
)]
fn or_else<M, C: Condition<M, In>>(self, or_else: C) -> Or<Self::System, C::System> {
self.or(or_else)
}
/// Returns a new run condition that only returns `true`
/// if `self` and `xnor` **both** return `false` or **both** return `true`.
///
/// # Examples
///
/// ```compile_fail
/// use bevy::prelude::*;
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum CoffeeMachineState {
/// Heating,
/// Brewing,
/// Inactive,
/// }
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum TeaKettleState {
/// Heating,
/// Steeping,
/// Inactive,
/// }
///
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # fn take_drink_orders() {}
/// app.add_systems(
/// // The take_drink_orders system will only execute if the `in_state(CoffeeMachineState::Inactive)`
/// // run condition and `in_state(TeaKettleState::Inactive)` run conditions both evaluate to `false`,
/// // or both evaluate to `true`.
/// take_drink_orders.run_if(
/// in_state(CoffeeMachineState::Inactive).xnor(in_state(TeaKettleState::Inactive))
/// ),
/// );
/// # app.run(&mut world);
/// ```
///
/// Equivalent logic can be achieved by using `not` in concert with `xor`:
///
/// ```compile_fail
/// app.add_systems(
/// take_drink_orders.run_if(
/// not(in_state(CoffeeMachineState::Inactive).xor(in_state(TeaKettleState::Inactive)))
/// ),
/// );
/// ```
fn xnor<M, C: Condition<M, In>>(self, xnor: C) -> Xnor<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(xnor);
let name = format!("!({} ^ {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
/// Returns a new run condition that only returns `true`
/// if either `self` or `xor` return `true`, but not both.
///
/// # Examples
///
/// ```compile_fail
/// use bevy::prelude::*;
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum CoffeeMachineState {
/// Heating,
/// Brewing,
/// Inactive,
/// }
///
/// #[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
/// pub enum TeaKettleState {
/// Heating,
/// Steeping,
/// Inactive,
/// }
///
/// # let mut app = Schedule::default();
/// # let mut world = World::new();
/// # fn prepare_beverage() {}
/// app.add_systems(
/// // The prepare_beverage system will only execute if either the `in_state(CoffeeMachineState::Inactive)`
/// // run condition or `in_state(TeaKettleState::Inactive)` run condition evaluates to `true`,
/// // but not both.
/// prepare_beverage.run_if(
/// in_state(CoffeeMachineState::Inactive).xor(in_state(TeaKettleState::Inactive))
/// ),
/// );
/// # app.run(&mut world);
/// ```
fn xor<M, C: Condition<M, In>>(self, xor: C) -> Xor<Self::System, C::System> {
let a = IntoSystem::into_system(self);
let b = IntoSystem::into_system(xor);
let name = format!("({} ^ {})", a.name(), b.name());
CombinatorSystem::new(a, b, Cow::Owned(name))
}
}
impl<Marker, In, F> Condition<Marker, In> for F where F: sealed::Condition<Marker, In> {}
@ -434,7 +730,7 @@ pub mod common_conditions {
/// // By default detecting changes will also trigger if the resource was
/// // just added, this won't work with my example so I will add a second
/// // condition to make sure the resource wasn't just added
/// .and_then(not(resource_added::<Counter>))
/// .and(not(resource_added::<Counter>))
/// ),
/// );
///
@ -487,7 +783,7 @@ pub mod common_conditions {
/// // By default detecting changes will also trigger if the resource was
/// // just added, this won't work with my example so I will add a second
/// // condition to make sure the resource wasn't just added
/// .and_then(not(resource_added::<Counter>))
/// .and(not(resource_added::<Counter>))
/// ),
/// );
///
@ -549,7 +845,7 @@ pub mod common_conditions {
/// // By default detecting changes will also trigger if the resource was
/// // just added, this won't work with my example so I will add a second
/// // condition to make sure the resource wasn't just added
/// .and_then(not(resource_added::<Counter>))
/// .and(not(resource_added::<Counter>))
/// ),
/// );
///
@ -809,15 +1105,27 @@ where
}
/// Combines the outputs of two systems using the `&&` operator.
pub type AndThen<A, B> = CombinatorSystem<AndThenMarker, A, B>;
pub type And<A, B> = CombinatorSystem<AndMarker, A, B>;
/// Combines and inverts the outputs of two systems using the `&&` and `!` operators.
pub type Nand<A, B> = CombinatorSystem<NandMarker, A, B>;
/// Combines and inverts the outputs of two systems using the `&&` and `!` operators.
pub type Nor<A, B> = CombinatorSystem<NorMarker, A, B>;
/// Combines the outputs of two systems using the `||` operator.
pub type OrElse<A, B> = CombinatorSystem<OrElseMarker, A, B>;
pub type Or<A, B> = CombinatorSystem<OrMarker, A, B>;
/// Combines and inverts the outputs of two systems using the `^` and `!` operators.
pub type Xnor<A, B> = CombinatorSystem<XnorMarker, A, B>;
/// Combines the outputs of two systems using the `^` operator.
pub type Xor<A, B> = CombinatorSystem<XorMarker, A, B>;
#[doc(hidden)]
pub struct AndThenMarker;
pub struct AndMarker;
impl<In, A, B> Combine<A, B> for AndThenMarker
impl<In, A, B> Combine<A, B> for AndMarker
where
In: Copy,
A: System<In = In, Out = bool>,
@ -836,9 +1144,51 @@ where
}
#[doc(hidden)]
pub struct OrElseMarker;
pub struct NandMarker;
impl<In, A, B> Combine<A, B> for OrElseMarker
impl<In, A, B> Combine<A, B> for NandMarker
where
In: Copy,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
type In = In;
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
) -> Self::Out {
!(a(input) && b(input))
}
}
#[doc(hidden)]
pub struct NorMarker;
impl<In, A, B> Combine<A, B> for NorMarker
where
In: Copy,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
type In = In;
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
) -> Self::Out {
!(a(input) || b(input))
}
}
#[doc(hidden)]
pub struct OrMarker;
impl<In, A, B> Combine<A, B> for OrMarker
where
In: Copy,
A: System<In = In, Out = bool>,
@ -856,6 +1206,48 @@ where
}
}
#[doc(hidden)]
pub struct XnorMarker;
impl<In, A, B> Combine<A, B> for XnorMarker
where
In: Copy,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
type In = In;
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
) -> Self::Out {
!(a(input) ^ b(input))
}
}
#[doc(hidden)]
pub struct XorMarker;
impl<In, A, B> Combine<A, B> for XorMarker
where
In: Copy,
A: System<In = In, Out = bool>,
B: System<In = In, Out = bool>,
{
type In = In;
type Out = bool;
fn combine(
input: Self::In,
a: impl FnOnce(<A as System>::In) -> <A as System>::Out,
b: impl FnOnce(<B as System>::In) -> <B as System>::Out,
) -> Self::Out {
a(input) ^ b(input)
}
}
#[cfg(test)]
mod tests {
use super::{common_conditions::*, Condition};
@ -874,6 +1266,10 @@ mod tests {
counter.0 += 1;
}
fn double_counter(mut counter: ResMut<Counter>) {
counter.0 *= 2;
}
fn every_other_time(mut has_ran: Local<bool>) -> bool {
*has_ran = !*has_ran;
*has_ran
@ -907,20 +1303,32 @@ mod tests {
}
#[test]
#[allow(deprecated)]
fn run_condition_combinators() {
let mut world = World::new();
world.init_resource::<Counter>();
let mut schedule = Schedule::default();
// Always run
schedule.add_systems(increment_counter.run_if(every_other_time.or_else(|| true)));
// Run every other cycle
schedule.add_systems(increment_counter.run_if(every_other_time.and_then(|| true)));
schedule.add_systems(
(
increment_counter.run_if(every_other_time.and(|| true)), // Run every odd cycle.
increment_counter.run_if(every_other_time.and_then(|| true)), // Run every odd cycle.
increment_counter.run_if(every_other_time.nand(|| false)), // Always run.
double_counter.run_if(every_other_time.nor(|| false)), // Run every even cycle.
increment_counter.run_if(every_other_time.or(|| true)), // Always run.
increment_counter.run_if(every_other_time.or_else(|| true)), // Always run.
increment_counter.run_if(every_other_time.xnor(|| true)), // Run every odd cycle.
double_counter.run_if(every_other_time.xnor(|| false)), // Run every even cycle.
increment_counter.run_if(every_other_time.xor(|| false)), // Run every odd cycle.
double_counter.run_if(every_other_time.xor(|| true)), // Run every even cycle.
)
.chain(),
);
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 2);
assert_eq!(world.resource::<Counter>().0, 7);
schedule.run(&mut world);
assert_eq!(world.resource::<Counter>().0, 3);
assert_eq!(world.resource::<Counter>().0, 72);
}
#[test]

View file

@ -320,8 +320,7 @@ where
// unblock this node's ancestors
while let Some(n) = unblock_stack.pop() {
if blocked.remove(&n) {
let unblock_predecessors =
unblock_together.entry(n).or_insert_with(HashSet::new);
let unblock_predecessors = unblock_together.entry(n).or_default();
unblock_stack.extend(unblock_predecessors.iter());
unblock_predecessors.clear();
}
@ -329,10 +328,7 @@ where
} else {
// if its descendants can be unblocked later, this node will be too
for successor in subgraph.neighbors(*node) {
unblock_together
.entry(successor)
.or_insert_with(HashSet::new)
.insert(*node);
unblock_together.entry(successor).or_default().insert(*node);
}
}

View file

@ -186,7 +186,7 @@ impl BlobVec {
/// # Safety
/// - index must be in bounds
/// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this [`BlobVec`]'s
/// `item_layout`, must have been previously allocated.
/// `item_layout`, must have been previously allocated.
#[inline]
pub unsafe fn initialize_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
debug_assert!(index < self.len());
@ -199,10 +199,10 @@ impl BlobVec {
/// # Safety
/// - index must be in-bounds
/// - the memory in the [`BlobVec`] starting at index `index`, of a size matching this
/// [`BlobVec`]'s `item_layout`, must have been previously initialized with an item matching
/// this [`BlobVec`]'s `item_layout`
/// [`BlobVec`]'s `item_layout`, must have been previously initialized with an item matching
/// this [`BlobVec`]'s `item_layout`
/// - the memory at `*value` must also be previously initialized with an item matching this
/// [`BlobVec`]'s `item_layout`
/// [`BlobVec`]'s `item_layout`
pub unsafe fn replace_unchecked(&mut self, index: usize, value: OwningPtr<'_>) {
debug_assert!(index < self.len());

View file

@ -127,6 +127,11 @@ where
self.system.apply_deferred(world);
}
#[inline]
fn queue_deferred(&mut self, world: crate::world::DeferredWorld) {
self.system.queue_deferred(world);
}
fn initialize(&mut self, world: &mut crate::prelude::World) {
self.system.initialize(world);
}

View file

@ -202,6 +202,12 @@ where
self.b.apply_deferred(world);
}
#[inline]
fn queue_deferred(&mut self, mut world: crate::world::DeferredWorld) {
self.a.queue_deferred(world.reborrow());
self.b.queue_deferred(world);
}
fn initialize(&mut self, world: &mut World) {
self.a.initialize(world);
self.b.initialize(world);

View file

@ -1,11 +1,13 @@
mod parallel_scope;
use super::{Deferred, IntoSystem, RegisterSystem, Resource};
use super::{Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource};
use crate::{
self as bevy_ecs,
bundle::Bundle,
component::ComponentId,
entity::{Entities, Entity},
event::Event,
observer::{Observer, TriggerEvent, TriggerTargets},
system::{RunSystemWithInput, SystemId},
world::command_queue::RawCommandQueue,
world::{Command, CommandQueue, EntityWorldMut, FromWorld, World},
@ -116,6 +118,17 @@ const _: () = {
world,
);
}
fn queue(
state: &mut Self::State,
system_meta: &bevy_ecs::system::SystemMeta,
world: bevy_ecs::world::DeferredWorld,
) {
<__StructFieldsAlias<'_, '_> as bevy_ecs::system::SystemParam>::queue(
&mut state.state,
system_meta,
world,
);
}
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
system_meta: &bevy_ecs::system::SystemMeta,
@ -150,7 +163,7 @@ impl<'w, 's> Commands<'w, 's> {
///
/// [system parameter]: crate::system::SystemParam
pub fn new(queue: &'s mut CommandQueue, world: &'w World) -> Self {
Self::new_from_entities(queue, world.entities())
Self::new_from_entities(queue, &world.entities)
}
/// Returns a new `Commands` instance from a [`CommandQueue`] and an [`Entities`] reference.
@ -735,6 +748,26 @@ impl<'w, 's> Commands<'w, 's> {
pub fn add<C: Command>(&mut self, command: C) {
self.push(command);
}
/// Sends a "global" [`Trigger`] without any targets. This will run any [`Observer`] of the `event` that
/// isn't scoped to specific targets.
pub fn trigger(&mut self, event: impl Event) {
self.add(TriggerEvent { event, targets: () });
}
/// Sends a [`Trigger`] for the given targets. This will run any [`Observer`] of the `event` that
/// watches those targets.
pub fn trigger_targets(&mut self, event: impl Event, targets: impl TriggerTargets) {
self.add(TriggerEvent { event, targets });
}
/// Spawn an [`Observer`] and returns the [`EntityCommands`] associated with the entity that stores the observer.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> EntityCommands {
self.spawn(Observer::new(observer))
}
}
/// A [`Command`] which gets executed for a given [`Entity`].
@ -1017,6 +1050,7 @@ impl EntityCommands<'_> {
}
/// Despawns the entity.
/// This will emit a warning if the entity does not exist.
///
/// See [`World::despawn`] for more details.
///
@ -1025,10 +1059,6 @@ impl EntityCommands<'_> {
/// This won't clean up external references to the entity (such as parent-child relationships
/// if you're using `bevy_hierarchy`), which may leave the world in an invalid state.
///
/// # Panics
///
/// The command will panic when applied if the associated entity does not exist.
///
/// # Example
///
/// ```
@ -1128,6 +1158,15 @@ impl EntityCommands<'_> {
pub fn commands(&mut self) -> Commands {
self.commands.reborrow()
}
/// Creates an [`Observer`](crate::observer::Observer) listening for a trigger of type `T` that targets this entity.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
system: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.add(observe(system));
self
}
}
impl<F> Command for F
@ -1288,6 +1327,16 @@ fn log_components(entity: Entity, world: &mut World) {
info!("Entity {:?}: {:?}", entity, debug_infos);
}
fn observe<E: Event, B: Bundle, M>(
observer: impl IntoObserverSystem<E, B, M>,
) -> impl EntityCommand {
move |entity, world: &mut World| {
if let Some(mut entity) = world.get_entity_mut(entity) {
entity.observe(observer);
}
}
}
#[cfg(test)]
#[allow(clippy::float_cmp, clippy::approx_constant)]
mod tests {

View file

@ -110,7 +110,7 @@ where
);
let out = self.func.run(world, input, params);
world.flush_commands();
world.flush();
let change_tick = world.change_tick.get_mut();
self.system_meta.last_run.set(*change_tick);
*change_tick = change_tick.wrapping_add(1);
@ -126,6 +126,13 @@ where
// might have buffers to apply, but this is handled by `PipeSystem`.
}
#[inline]
fn queue_deferred(&mut self, _world: crate::world::DeferredWorld) {
// "pure" exclusive systems do not have any buffers to apply.
// Systems made by piping a normal system with an exclusive system
// might have buffers to apply, but this is handled by `PipeSystem`.
}
#[inline]
fn initialize(&mut self, world: &mut World) {
self.system_meta.last_run = world.change_tick().relative_to(Tick::MAX);

View file

@ -5,7 +5,7 @@ use crate::{
query::{Access, FilteredAccessSet},
schedule::{InternedSystemSet, SystemSet},
system::{check_system_change_tick, ReadOnlySystemParam, System, SystemParam, SystemParamItem},
world::{unsafe_world_cell::UnsafeWorldCell, World, WorldId},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World, WorldId},
};
use bevy_utils::all_tuples;
@ -399,8 +399,8 @@ where
F: SystemParamFunction<Marker>,
{
func: F,
param_state: Option<<F::Param as SystemParam>::State>,
system_meta: SystemMeta,
pub(crate) param_state: Option<<F::Param as SystemParam>::State>,
pub(crate) system_meta: SystemMeta,
world_id: Option<WorldId>,
archetype_generation: ArchetypeGeneration,
// NOTE: PhantomData<fn()-> T> gives this safe Send/Sync impls
@ -542,6 +542,12 @@ where
F::Param::apply(param_state, &self.system_meta, world);
}
#[inline]
fn queue_deferred(&mut self, world: DeferredWorld) {
let param_state = self.param_state.as_mut().expect(Self::PARAM_MESSAGE);
F::Param::queue(param_state, &self.system_meta, world);
}
#[inline]
fn initialize(&mut self, world: &mut World) {
if let Some(id) = self.world_id {

View file

@ -108,6 +108,7 @@ mod commands;
mod exclusive_function_system;
mod exclusive_system_param;
mod function_system;
mod observer_system;
mod query;
#[allow(clippy::module_inception)]
mod system;
@ -124,6 +125,7 @@ pub use commands::*;
pub use exclusive_function_system::*;
pub use exclusive_system_param::*;
pub use function_system::*;
pub use observer_system::*;
pub use query::*;
pub use system::*;
pub use system_name::*;
@ -1682,7 +1684,7 @@ mod tests {
res.0 += 2;
},
)
.distributive_run_if(resource_exists::<A>.or_else(resource_exists::<B>)),
.distributive_run_if(resource_exists::<A>.or(resource_exists::<B>)),
);
sched.initialize(&mut world).unwrap();
sched.run(&mut world);

View file

@ -0,0 +1,71 @@
use bevy_utils::all_tuples;
use crate::{
prelude::{Bundle, Trigger},
system::{System, SystemParam, SystemParamFunction, SystemParamItem},
};
use super::IntoSystem;
/// Implemented for systems that have an [`Observer`] as the first argument.
pub trait ObserverSystem<E: 'static, B: Bundle>:
System<In = Trigger<'static, E, B>, Out = ()> + Send + 'static
{
}
impl<E: 'static, B: Bundle, T: System<In = Trigger<'static, E, B>, Out = ()> + Send + 'static>
ObserverSystem<E, B> for T
{
}
/// Implemented for systems that convert into [`ObserverSystem`].
pub trait IntoObserverSystem<E: 'static, B: Bundle, M>: Send + 'static {
/// The type of [`System`] that this instance converts into.
type System: ObserverSystem<E, B>;
/// Turns this value into its corresponding [`System`].
fn into_system(this: Self) -> Self::System;
}
impl<S: IntoSystem<Trigger<'static, E, B>, (), M> + Send + 'static, M, E: 'static, B: Bundle>
IntoObserverSystem<E, B, M> for S
where
S::System: ObserverSystem<E, B>,
{
type System = <S as IntoSystem<Trigger<'static, E, B>, (), M>>::System;
fn into_system(this: Self) -> Self::System {
IntoSystem::into_system(this)
}
}
macro_rules! impl_system_function {
($($param: ident),*) => {
#[allow(non_snake_case)]
impl<E: 'static, B: Bundle, Func: Send + Sync + 'static, $($param: SystemParam),*> SystemParamFunction<fn(Trigger<E, B>, $($param,)*)> for Func
where
for <'a> &'a mut Func:
FnMut(Trigger<E, B>, $($param),*) +
FnMut(Trigger<E, B>, $(SystemParamItem<$param>),*)
{
type In = Trigger<'static, E, B>;
type Out = ();
type Param = ($($param,)*);
#[inline]
fn run(&mut self, input: Trigger<'static, E, B>, param_value: SystemParamItem< ($($param,)*)>) {
#[allow(clippy::too_many_arguments)]
fn call_inner<E: 'static, B: Bundle, $($param,)*>(
mut f: impl FnMut(Trigger<'static, E, B>, $($param,)*),
input: Trigger<'static, E, B>,
$($param: $param,)*
){
f(input, $($param,)*)
}
let ($($param,)*) = param_value;
call_inner(self, input, $($param),*)
}
}
}
}
all_tuples!(impl_system_function, 0, 16, F);

View file

@ -1385,9 +1385,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Returns a [`QueryLens`] that can be used to get a query with the combined fetch.
///
/// For example, this can take a `Query<&A>` and a `Queryy<&B>` and return a `Query<&A, &B>`.
/// The returned query will only return items with both `A` and `B`. Note that since filter
/// are dropped, non-archetypal filters like `Added` and `Changed` will no be respected.
/// For example, this can take a `Query<&A>` and a `Query<&B>` and return a `Query<(&A, &B)>`.
/// The returned query will only return items with both `A` and `B`. Note that since filters
/// are dropped, non-archetypal filters like `Added` and `Changed` will not be respected.
/// To maintain or change filter terms see `Self::join_filtered`.
///
/// ## Example
@ -1446,7 +1446,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> {
/// Equivalent to [`Self::join`] but also includes a [`QueryFilter`] type.
///
/// Note that the lens with iterate a subset of the original queries tables
/// Note that the lens with iterate a subset of the original queries' tables
/// and archetypes. This means that additional archetypal query terms like
/// `With` and `Without` will not necessarily be respected and non-archetypal
/// terms like `Added` and `Changed` will only be respected if they are in

View file

@ -4,6 +4,7 @@ use core::fmt::Debug;
use crate::component::Tick;
use crate::schedule::InternedSystemSet;
use crate::world::unsafe_world_cell::UnsafeWorldCell;
use crate::world::DeferredWorld;
use crate::{archetype::ArchetypeComponentId, component::ComponentId, query::Access, world::World};
use std::any::TypeId;
@ -89,6 +90,10 @@ pub trait System: Send + Sync + 'static {
/// This is where [`Commands`](crate::system::Commands) get applied.
fn apply_deferred(&mut self, world: &mut World);
/// Enqueues any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers)
/// of this system into the world's command buffer.
fn queue_deferred(&mut self, world: DeferredWorld);
/// Initialize the system.
fn initialize(&mut self, _world: &mut World);

View file

@ -11,7 +11,7 @@ use crate::{
ReadOnlyQueryData,
},
system::{Query, SystemMeta},
world::{unsafe_world_cell::UnsafeWorldCell, FromWorld, World},
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World},
};
use bevy_ecs_macros::impl_param_set;
pub use bevy_ecs_macros::Resource;
@ -159,6 +159,11 @@ pub unsafe trait SystemParam: Sized {
#[allow(unused_variables)]
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {}
/// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred).
#[inline]
#[allow(unused_variables)]
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {}
/// Creates a parameter to be passed into a [`SystemParamFunction`].
///
/// [`SystemParamFunction`]: super::SystemParamFunction
@ -712,6 +717,27 @@ unsafe impl SystemParam for &'_ World {
}
}
/// SAFETY: `DeferredWorld` can read all components and resources but cannot be used to gain any other mutable references.
unsafe impl<'w> SystemParam for DeferredWorld<'w> {
type State = ();
type Item<'world, 'state> = DeferredWorld<'world>;
fn init_state(_world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
system_meta.component_access_set.read_all();
system_meta.component_access_set.write_all();
system_meta.set_has_deferred();
}
unsafe fn get_param<'world, 'state>(
_state: &'state mut Self::State,
_system_meta: &SystemMeta,
world: UnsafeWorldCell<'world>,
_change_tick: Tick,
) -> Self::Item<'world, 'state> {
world.into_deferred()
}
}
/// A system local [`SystemParam`].
///
/// A local may only be accessed by the system itself and is therefore not visible to other systems.
@ -848,6 +874,8 @@ impl<'w, T: FromWorld + Send + 'static> BuildableSystemParam for Local<'w, T> {
pub trait SystemBuffer: FromWorld + Send + 'static {
/// Applies any deferred mutations to the [`World`].
fn apply(&mut self, system_meta: &SystemMeta, world: &mut World);
/// Queues any deferred mutations to be applied at the next [`apply_deferred`](crate::prelude::apply_deferred).
fn queue(&mut self, _system_meta: &SystemMeta, _world: DeferredWorld) {}
}
/// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] during
@ -1012,6 +1040,10 @@ unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> {
state.get().apply(system_meta, world);
}
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
state.get().queue(system_meta, world);
}
unsafe fn get_param<'w, 's>(
state: &'s mut Self::State,
_system_meta: &SystemMeta,
@ -1424,6 +1456,11 @@ macro_rules! impl_system_param_tuple {
$($param::apply($param, _system_meta, _world);)*
}
#[inline]
fn queue(($($param,)*): &mut Self::State, _system_meta: &SystemMeta, mut _world: DeferredWorld) {
$($param::queue($param, _system_meta, _world.reborrow());)*
}
#[inline]
#[allow(clippy::unused_unit)]
unsafe fn get_param<'w, 's>(

View file

@ -3,6 +3,7 @@ use crate::system::{SystemBuffer, SystemMeta};
use std::{
fmt::Debug,
mem::MaybeUninit,
panic::{self, AssertUnwindSafe},
ptr::{addr_of_mut, NonNull},
};
@ -11,6 +12,8 @@ use bevy_utils::tracing::warn;
use crate::world::{Command, World};
use super::DeferredWorld;
struct CommandMeta {
/// SAFETY: The `value` must point to a value of type `T: Command`,
/// where `T` is some specific type that was used to produce this metadata.
@ -18,11 +21,8 @@ struct CommandMeta {
/// `world` is optional to allow this one function pointer to perform double-duty as a drop.
///
/// Advances `cursor` by the size of `T` in bytes.
consume_command_and_get_size: unsafe fn(
value: OwningPtr<Unaligned>,
world: Option<NonNull<World>>,
cursor: NonNull<usize>,
),
consume_command_and_get_size:
unsafe fn(value: OwningPtr<Unaligned>, world: Option<NonNull<World>>, cursor: &mut usize),
}
/// Densely and efficiently stores a queue of heterogenous types implementing [`Command`].
@ -41,6 +41,7 @@ pub struct CommandQueue {
// be passed to the corresponding `CommandMeta.apply_command_and_get_size` fn pointer.
pub(crate) bytes: Vec<MaybeUninit<u8>>,
pub(crate) cursor: usize,
pub(crate) panic_recovery: Vec<MaybeUninit<u8>>,
}
/// Wraps pointers to a [`CommandQueue`], used internally to avoid stacked borrow rules when
@ -49,6 +50,7 @@ pub struct CommandQueue {
pub(crate) struct RawCommandQueue {
pub(crate) bytes: NonNull<Vec<MaybeUninit<u8>>>,
pub(crate) cursor: NonNull<usize>,
pub(crate) panic_recovery: NonNull<Vec<MaybeUninit<u8>>>,
}
// CommandQueue needs to implement Debug manually, rather than deriving it, because the derived impl just prints
@ -117,6 +119,7 @@ impl CommandQueue {
RawCommandQueue {
bytes: NonNull::new_unchecked(addr_of_mut!(self.bytes)),
cursor: NonNull::new_unchecked(addr_of_mut!(self.cursor)),
panic_recovery: NonNull::new_unchecked(addr_of_mut!(self.panic_recovery)),
}
}
}
@ -130,6 +133,7 @@ impl RawCommandQueue {
Self {
bytes: NonNull::new_unchecked(Box::into_raw(Box::default())),
cursor: NonNull::new_unchecked(Box::into_raw(Box::new(0usize))),
panic_recovery: NonNull::new_unchecked(Box::into_raw(Box::default())),
}
}
}
@ -164,17 +168,23 @@ impl RawCommandQueue {
}
let meta = CommandMeta {
consume_command_and_get_size: |command, world, mut cursor| {
// SAFETY: Pointer is assured to be valid in `CommandQueue.apply_or_drop_queued`
unsafe { *cursor.as_mut() += std::mem::size_of::<C>() }
consume_command_and_get_size: |command, world, cursor| {
*cursor += std::mem::size_of::<C>();
// SAFETY: According to the invariants of `CommandMeta.consume_command_and_get_size`,
// `command` must point to a value of type `C`.
let command: C = unsafe { command.read_unaligned() };
match world {
// Apply command to the provided world...
// SAFETY: Calller ensures pointer is not null
Some(mut world) => command.apply(unsafe { world.as_mut() }),
Some(mut world) => {
// SAFETY: Caller ensures pointer is not null
let world = unsafe { world.as_mut() };
command.apply(world);
// The command may have queued up world commands, which we flush here to ensure they are also picked up.
// If the current command queue already the World Command queue, this will still behave appropriately because the global cursor
// is still at the current `stop`, ensuring only the newly queued Commands will be applied.
world.flush();
}
// ...or discard it.
None => drop(command),
}
@ -222,50 +232,79 @@ impl RawCommandQueue {
pub(crate) unsafe fn apply_or_drop_queued(&mut self, world: Option<NonNull<World>>) {
// SAFETY: If this is the command queue on world, world will not be dropped as we have a mutable reference
// If this is not the command queue on world we have exclusive ownership and self will not be mutated
while *self.cursor.as_ref() < self.bytes.as_ref().len() {
let start = *self.cursor.as_ref();
let stop = self.bytes.as_ref().len();
let mut local_cursor = start;
// SAFETY: we are setting the global cursor to the current length to prevent the executing commands from applying
// the remaining commands currently in this list. This is safe.
*self.cursor.as_mut() = stop;
while local_cursor < stop {
// SAFETY: The cursor is either at the start of the buffer, or just after the previous command.
// Since we know that the cursor is in bounds, it must point to the start of a new command.
let meta = unsafe {
self.bytes
.as_mut()
.as_mut_ptr()
.add(*self.cursor.as_ref())
.add(local_cursor)
.cast::<CommandMeta>()
.read_unaligned()
};
// Advance to the bytes just after `meta`, which represent a type-erased command.
// SAFETY: For most types of `Command`, the pointer immediately following the metadata
// is guaranteed to be in bounds. If the command is a zero-sized type (ZST), then the cursor
// might be 1 byte past the end of the buffer, which is safe.
unsafe { *self.cursor.as_mut() += std::mem::size_of::<CommandMeta>() };
local_cursor += std::mem::size_of::<CommandMeta>();
// Construct an owned pointer to the command.
// SAFETY: It is safe to transfer ownership out of `self.bytes`, since the increment of `cursor` above
// guarantees that nothing stored in the buffer will get observed after this function ends.
// `cmd` points to a valid address of a stored command, so it must be non-null.
let cmd = unsafe {
OwningPtr::<Unaligned>::new(std::ptr::NonNull::new_unchecked(
self.bytes
.as_mut()
.as_mut_ptr()
.add(*self.cursor.as_ref())
.cast(),
self.bytes.as_mut().as_mut_ptr().add(local_cursor).cast(),
))
};
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
// This also advances the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
unsafe { (meta.consume_command_and_get_size)(cmd, world, self.cursor) };
let result = panic::catch_unwind(AssertUnwindSafe(|| {
// SAFETY: The data underneath the cursor must correspond to the type erased in metadata,
// since they were stored next to each other by `.push()`.
// For ZSTs, the type doesn't matter as long as the pointer is non-null.
// This also advances the cursor past the command. For ZSTs, the cursor will not move.
// At this point, it will either point to the next `CommandMeta`,
// or the cursor will be out of bounds and the loop will end.
unsafe { (meta.consume_command_and_get_size)(cmd, world, &mut local_cursor) };
}));
if let Err(payload) = result {
// local_cursor now points to the location _after_ the panicked command.
// Add the remaining commands that _would have_ been applied to the
// panic_recovery queue.
//
// This uses `current_stop` instead of `stop` to account for any commands
// that were queued _during_ this panic.
//
// This is implemented in such a way that if apply_or_drop_queued() are nested recursively in,
// an applied Command, the correct command order will be retained.
let panic_recovery = self.panic_recovery.as_mut();
let bytes = self.bytes.as_mut();
let current_stop = bytes.len();
panic_recovery.extend_from_slice(&bytes[local_cursor..current_stop]);
bytes.set_len(start);
*self.cursor.as_mut() = start;
// This was the "top of the apply stack". If we are _not_ at the top of the apply stack,
// when we call`resume_unwind" the caller "closer to the top" will catch the unwind and do this check,
// until we reach the top.
if start == 0 {
bytes.append(panic_recovery);
}
panic::resume_unwind(payload);
}
}
// Reset the buffer, so it can be reused after this function ends.
// SAFETY: `set_len(0)` is always valid.
// Reset the buffer: all commands past the original `start` cursor have been applied.
// SAFETY: we are setting the length of bytes to the original length, minus the length of the original
// list of commands being considered. All bytes remaining in the Vec are still valid, unapplied commands.
unsafe {
self.bytes.as_mut().set_len(0);
*self.cursor.as_mut() = 0;
self.bytes.as_mut().set_len(start);
*self.cursor.as_mut() = start;
};
}
}
@ -287,11 +326,18 @@ impl SystemBuffer for CommandQueue {
let _span_guard = _system_meta.commands_span.enter();
self.apply(world);
}
#[inline]
fn queue(&mut self, _system_meta: &SystemMeta, mut world: DeferredWorld) {
world.commands().append(self);
}
}
#[cfg(test)]
mod test {
use super::*;
use crate as bevy_ecs;
use crate::system::Resource;
use std::{
panic::AssertUnwindSafe,
sync::{
@ -412,10 +458,6 @@ mod test {
queue.apply(&mut world);
}));
// even though the first command panicking.
// the cursor was incremented.
assert!(queue.cursor > 0);
// Even though the first command panicked, it's still ok to push
// more commands.
queue.push(SpawnCommand);
@ -424,6 +466,37 @@ mod test {
assert_eq!(world.entities().len(), 3);
}
#[test]
fn test_command_queue_inner_nested_panic_safe() {
std::panic::set_hook(Box::new(|_| {}));
#[derive(Resource, Default)]
struct Order(Vec<usize>);
let mut world = World::new();
world.init_resource::<Order>();
fn add_index(index: usize) -> impl Command {
move |world: &mut World| world.resource_mut::<Order>().0.push(index)
}
world.commands().add(add_index(1));
world.commands().add(|world: &mut World| {
world.commands().add(add_index(2));
world.commands().add(PanicCommand("I panic!".to_owned()));
world.commands().add(add_index(3));
world.flush_commands();
});
world.commands().add(add_index(4));
let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
world.flush_commands();
}));
world.commands().add(add_index(5));
world.flush_commands();
assert_eq!(&world.resource::<Order>().0, &[1, 2, 3, 4, 5]);
}
// NOTE: `CommandQueue` is `Send` because `Command` is send.
// If the `Command` trait gets reworked to be non-send, `CommandQueue`
// should be reworked.

View file

@ -0,0 +1,23 @@
use super::*;
use crate::{self as bevy_ecs};
/// Internal components used by bevy with a fixed component id.
/// Constants are used to skip [`TypeId`] lookups in hot paths.
/// [`ComponentId`] for [`OnAdd`]
pub const ON_ADD: ComponentId = ComponentId::new(0);
/// [`ComponentId`] for [`OnInsert`]
pub const ON_INSERT: ComponentId = ComponentId::new(1);
/// [`ComponentId`] for [`OnRemove`]
pub const ON_REMOVE: ComponentId = ComponentId::new(2);
/// Trigger emitted when a component is added to an entity.
#[derive(Event)]
pub struct OnAdd;
/// Trigger emitted when a component is inserted on to to an entity.
#[derive(Event)]
pub struct OnInsert;
/// Trigger emitted when a component is removed from an entity.
#[derive(Event)]
pub struct OnRemove;

View file

@ -1,10 +1,12 @@
use std::ops::Deref;
use crate::{
archetype::Archetype,
change_detection::MutUntyped,
component::ComponentId,
entity::Entity,
event::{Event, EventId, Events, SendBatchIds},
observer::{Observers, TriggerTargets},
prelude::{Component, QueryState},
query::{QueryData, QueryFilter},
system::{Commands, Query, Resource},
@ -52,6 +54,12 @@ impl<'w> From<&'w mut World> for DeferredWorld<'w> {
}
impl<'w> DeferredWorld<'w> {
/// Reborrow self as a new instance of [`DeferredWorld`]
#[inline]
pub fn reborrow(&mut self) -> DeferredWorld {
DeferredWorld { world: self.world }
}
/// Creates a [`Commands`] instance that pushes to the world's command queue
#[inline]
pub fn commands(&mut self) -> Commands {
@ -65,37 +73,42 @@ impl<'w> DeferredWorld<'w> {
/// Returns `None` if the `entity` does not have a [`Component`] of the given type.
#[inline]
pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<Mut<T>> {
// SAFETY: &mut self ensure that there are no outstanding accesses to the component
// SAFETY:
// - `as_unsafe_world_cell` is the only thing that is borrowing world
// - `as_unsafe_world_cell` provides mutable permission to everything
// - `&mut self` ensures no other borrows on world data
unsafe { self.world.get_entity(entity)?.get_mut() }
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
#[inline]
#[track_caller]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
#[track_caller]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// Returns [`None`] if the `entity` does not exist.
/// Instead of unwrapping the value returned from this function, prefer [`Self::entity_mut`].
#[inline]
pub fn get_entity_mut(&mut self, entity: Entity) -> Option<EntityMut> {
let location = self.entities.get(entity)?;
// SAFETY: `entity` exists and `location` is that entity's location
Some(unsafe { EntityMut::new(UnsafeEntityCell::new(self.world, entity, location)) })
// SAFETY: if the Entity is invalid, the function returns early.
// Additionally, Entities::get(entity) returns the correct EntityLocation if the entity exists.
let entity_cell = UnsafeEntityCell::new(self.as_unsafe_world_cell(), entity, location);
// SAFETY: The UnsafeEntityCell has read access to the entire world.
let entity_ref = unsafe { EntityMut::new(entity_cell) };
Some(entity_ref)
}
/// Retrieves an [`EntityMut`] that exposes read and write operations for the given `entity`.
/// This will panic if the `entity` does not exist. Use [`Self::get_entity_mut`] if you want
/// to check for entity existence instead of implicitly panic-ing.
#[inline]
pub fn entity_mut(&mut self, entity: Entity) -> EntityMut {
#[inline(never)]
#[cold]
fn panic_no_entity(entity: Entity) -> ! {
panic!("Entity {entity:?} does not exist");
}
match self.get_entity_mut(entity) {
Some(entity) => entity,
None => panic_no_entity(entity),
}
}
/// Returns [`Query`] for the given [`QueryState`], which is used to efficiently
@ -266,14 +279,17 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub(crate) unsafe fn trigger_on_add(
&mut self,
archetype: &Archetype,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_add {
hook(DeferredWorld { world: self.world }, entity, component_id);
if archetype.has_add_hook() {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_add {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}
@ -285,14 +301,17 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub(crate) unsafe fn trigger_on_insert(
&mut self,
archetype: &Archetype,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_insert {
hook(DeferredWorld { world: self.world }, entity, component_id);
if archetype.has_insert_hook() {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_insert {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}
@ -304,15 +323,67 @@ impl<'w> DeferredWorld<'w> {
#[inline]
pub(crate) unsafe fn trigger_on_remove(
&mut self,
archetype: &Archetype,
entity: Entity,
targets: impl Iterator<Item = ComponentId>,
) {
for component_id in targets {
// SAFETY: Caller ensures that these components exist
let hooks = unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_remove {
hook(DeferredWorld { world: self.world }, entity, component_id);
if archetype.has_remove_hook() {
for component_id in targets {
let hooks =
// SAFETY: Caller ensures that these components exist
unsafe { self.world.components().get_info_unchecked(component_id) }.hooks();
if let Some(hook) = hooks.on_remove {
hook(DeferredWorld { world: self.world }, entity, component_id);
}
}
}
}
/// Triggers all event observers for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure observers listening for `event` can accept ZST pointers
#[inline]
pub(crate) unsafe fn trigger_observers(
&mut self,
event: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
) {
Observers::invoke(self.reborrow(), event, entity, components, &mut ());
}
/// Triggers all event observers for [`ComponentId`] in target.
///
/// # Safety
/// Caller must ensure `E` is accessible as the type represented by `event`
#[inline]
pub(crate) unsafe fn trigger_observers_with_data<E>(
&mut self,
event: ComponentId,
entity: Entity,
components: impl Iterator<Item = ComponentId>,
data: &mut E,
) {
Observers::invoke(self.reborrow(), event, entity, components, data);
}
/// Sends a "global" [`Trigger`] without any targets.
pub fn trigger<T: Event>(&mut self, trigger: impl Event) {
self.commands().trigger(trigger);
}
/// Sends a [`Trigger`] with the given `targets`.
pub fn trigger_targets(&mut self, trigger: impl Event, targets: impl TriggerTargets) {
self.commands().trigger_targets(trigger, targets);
}
/// Gets an [`UnsafeWorldCell`] containing the underlying world.
///
/// # Safety
/// - must only be used to to make non-structural ECS changes
#[inline]
pub(crate) fn as_unsafe_world_cell(&mut self) -> UnsafeWorldCell {
self.world
}
}

View file

@ -4,16 +4,19 @@ use crate::{
change_detection::MutUntyped,
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
entity::{Entities, Entity, EntityLocation},
event::Event,
observer::{Observer, Observers},
query::Access,
removal_detection::RemovedComponentEvents,
storage::Storages,
world::{Mut, World},
system::IntoObserverSystem,
world::{DeferredWorld, Mut, World},
};
use bevy_ptr::{OwningPtr, Ptr};
use std::{any::TypeId, marker::PhantomData};
use thiserror::Error;
use super::{unsafe_world_cell::UnsafeEntityCell, Ref};
use super::{unsafe_world_cell::UnsafeEntityCell, Ref, ON_REMOVE};
/// A read-only reference to a particular [`Entity`] and all of its components.
///
@ -84,7 +87,7 @@ impl<'w> EntityRef<'w> {
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.0.contains_id(component_id)
@ -323,7 +326,7 @@ impl<'w> EntityMut<'w> {
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.0.contains_id(component_id)
@ -618,7 +621,7 @@ impl<'w> EntityWorldMut<'w> {
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.as_unsafe_entity_cell_readonly()
@ -876,6 +879,7 @@ impl<'w> EntityWorldMut<'w> {
&mut world.archetypes,
storages,
components,
&world.observers,
old_location.archetype_id,
bundle_info,
false,
@ -898,11 +902,14 @@ impl<'w> EntityWorldMut<'w> {
)
};
if old_archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(entity, bundle_info.iter_components());
}
// SAFETY: all bundle components exist in World
unsafe {
trigger_on_remove_hooks_and_observers(
&mut deferred_world,
old_archetype,
entity,
bundle_info,
);
}
let archetypes = &mut world.archetypes;
@ -1053,6 +1060,7 @@ impl<'w> EntityWorldMut<'w> {
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
location.archetype_id,
bundle_info,
// components from the bundle that are not present on the entity are ignored
@ -1075,11 +1083,14 @@ impl<'w> EntityWorldMut<'w> {
)
};
if old_archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(entity, bundle_info.iter_components());
}
// SAFETY: all bundle components exist in World
unsafe {
trigger_on_remove_hooks_and_observers(
&mut deferred_world,
old_archetype,
entity,
bundle_info,
);
}
let old_archetype = &world.archetypes[location.archetype_id];
@ -1209,10 +1220,11 @@ impl<'w> EntityWorldMut<'w> {
(&*archetype, world.into_deferred())
};
if archetype.has_on_remove() {
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(self.entity, archetype.components());
// SAFETY: All components in the archetype exist in world
unsafe {
deferred_world.trigger_on_remove(archetype, self.entity, archetype.components());
if archetype.has_remove_observer() {
deferred_world.trigger_observers(ON_REMOVE, self.entity, archetype.components());
}
}
@ -1276,12 +1288,12 @@ impl<'w> EntityWorldMut<'w> {
world.archetypes[moved_location.archetype_id]
.set_entity_table_row(moved_location.archetype_row, table_row);
}
world.flush_commands();
world.flush();
}
/// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush_commands`]
/// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`]
pub fn flush(self) -> Entity {
self.world.flush_commands();
self.world.flush();
self.entity
}
@ -1397,6 +1409,30 @@ impl<'w> EntityWorldMut<'w> {
})
}
}
/// Creates an [`Observer`](crate::observer::Observer) listening for events of type `E` targeting this entity.
/// In order to trigger the callback the entity must also match the query when the event is fired.
pub fn observe<E: Event, B: Bundle, M>(
&mut self,
observer: impl IntoObserverSystem<E, B, M>,
) -> &mut Self {
self.world
.spawn(Observer::new(observer).with_entity(self.entity));
self
}
}
/// SAFETY: all components in the archetype must exist in world
unsafe fn trigger_on_remove_hooks_and_observers(
deferred_world: &mut DeferredWorld,
archetype: &Archetype,
entity: Entity,
bundle_info: &BundleInfo,
) {
deferred_world.trigger_on_remove(archetype, entity, bundle_info.iter_components());
if archetype.has_remove_observer() {
deferred_world.trigger_observers(ON_REMOVE, entity, bundle_info.iter_components());
}
}
/// A view into a single entity and component in a world, which may either be vacant or occupied.
@ -1759,7 +1795,7 @@ impl<'w> FilteredEntityRef<'w> {
/// # Safety
/// - No `&mut World` can exist from the underlying `UnsafeWorldCell`
/// - If `access` takes read access to a component no mutable reference to that
/// component can exist at the same time as the returned [`FilteredEntityMut`]
/// component can exist at the same time as the returned [`FilteredEntityMut`]
/// - If `access` takes any access for a component `entity` must have that component.
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: Access<ComponentId>) -> Self {
Self { entity, access }
@ -1815,7 +1851,7 @@ impl<'w> FilteredEntityRef<'w> {
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.entity.contains_id(component_id)
@ -2002,9 +2038,9 @@ impl<'w> FilteredEntityMut<'w> {
/// # Safety
/// - No `&mut World` can exist from the underlying `UnsafeWorldCell`
/// - If `access` takes read access to a component no mutable reference to that
/// component can exist at the same time as the returned [`FilteredEntityMut`]
/// component can exist at the same time as the returned [`FilteredEntityMut`]
/// - If `access` takes write access to a component, no reference to that component
/// may exist at the same time as the returned [`FilteredEntityMut`]
/// may exist at the same time as the returned [`FilteredEntityMut`]
/// - If `access` takes any access for a component `entity` must have that component.
pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: Access<ComponentId>) -> Self {
Self { entity, access }
@ -2072,7 +2108,7 @@ impl<'w> FilteredEntityMut<'w> {
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(&self, component_id: ComponentId) -> bool {
self.entity.contains_id(component_id)
@ -2246,7 +2282,7 @@ pub enum TryFromFilteredError {
/// # Safety
///
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
/// [`BundleInfo`] used to construct [`BundleInserter`]
/// [`BundleInfo`] used to construct [`BundleInserter`]
/// - [`Entity`] must correspond to [`EntityLocation`]
unsafe fn insert_dynamic_bundle<
'a,
@ -2292,6 +2328,7 @@ unsafe fn remove_bundle_from_archetype(
archetypes: &mut Archetypes,
storages: &mut Storages,
components: &Components,
observers: &Observers,
archetype_id: ArchetypeId,
bundle_info: &BundleInfo,
intersection: bool,
@ -2338,8 +2375,8 @@ unsafe fn remove_bundle_from_archetype(
// sort removed components so we can do an efficient "sorted remove". archetype
// components are already sorted
removed_table_components.sort();
removed_sparse_set_components.sort();
removed_table_components.sort_unstable();
removed_sparse_set_components.sort_unstable();
next_table_components = current_archetype.table_components().collect();
next_sparse_set_components = current_archetype.sparse_set_components().collect();
sorted_remove(&mut next_table_components, &removed_table_components);
@ -2362,6 +2399,7 @@ unsafe fn remove_bundle_from_archetype(
let new_archetype_id = archetypes.get_id_or_insert(
components,
observers,
next_table_id,
next_table_components,
next_sparse_set_components,

View file

@ -1,20 +1,25 @@
//! Defines the [`World`] and APIs for accessing it directly.
pub(crate) mod command_queue;
mod component_constants;
mod deferred_world;
mod entity_ref;
pub mod error;
mod identifier;
mod spawn_batch;
pub mod unsafe_world_cell;
pub use crate::change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD};
pub use crate::world::command_queue::CommandQueue;
use crate::{component::ComponentInitializer, entity::EntityHashSet};
pub use crate::{
change_detection::{Mut, Ref, CHECK_TICK_THRESHOLD},
world::command_queue::CommandQueue,
};
pub use component_constants::*;
pub use deferred_world::DeferredWorld;
pub use entity_ref::{
EntityMut, EntityRef, EntityWorldMut, Entry, FilteredEntityMut, FilteredEntityRef,
OccupiedEntry, VacantEntry,
};
pub use identifier::WorldId;
pub use spawn_batch::*;
use crate::{
@ -25,8 +30,9 @@ use crate::{
Component, ComponentDescriptor, ComponentHooks, ComponentId, ComponentInfo, ComponentTicks,
Components, Tick,
},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityHashSet, EntityLocation},
event::{Event, EventId, Events, SendBatchIds},
observer::Observers,
query::{DebugCheckedUnwrap, QueryData, QueryEntityError, QueryFilter, QueryState},
removal_detection::RemovedComponentEvents,
schedule::{Schedule, ScheduleLabel, Schedules},
@ -43,10 +49,7 @@ use std::{
mem::MaybeUninit,
sync::atomic::{AtomicU32, Ordering},
};
mod identifier;
use self::unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
pub use identifier::WorldId;
use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell};
/// A [`World`] mutation.
///
@ -110,30 +113,36 @@ pub struct World {
pub(crate) archetypes: Archetypes,
pub(crate) storages: Storages,
pub(crate) bundles: Bundles,
pub(crate) observers: Observers,
pub(crate) removed_components: RemovedComponentEvents,
pub(crate) change_tick: AtomicU32,
pub(crate) last_change_tick: Tick,
pub(crate) last_check_tick: Tick,
pub(crate) last_trigger_id: u32,
pub(crate) command_queue: RawCommandQueue,
}
impl Default for World {
fn default() -> Self {
Self {
let mut world = Self {
id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"),
entities: Entities::new(),
components: Default::default(),
archetypes: Archetypes::new(),
storages: Default::default(),
bundles: Default::default(),
observers: Observers::default(),
removed_components: Default::default(),
// Default value is `1`, and `last_change_tick`s default to `0`, such that changes
// are detected on first system runs and for direct world queries.
change_tick: AtomicU32::new(1),
last_change_tick: Tick::new(0),
last_check_tick: Tick::new(0),
last_trigger_id: 0,
command_queue: RawCommandQueue::new(),
}
};
world.bootstrap();
world
}
}
@ -149,6 +158,14 @@ impl Drop for World {
}
impl World {
/// This performs initialization that _must_ happen for every [`World`] immediately upon creation (such as claiming specific component ids).
/// This _must_ be run as part of constructing a [`World`], before it is returned to the caller.
#[inline]
fn bootstrap(&mut self) {
assert_eq!(ON_ADD, self.init_component::<OnAdd>());
assert_eq!(ON_INSERT, self.init_component::<OnInsert>());
assert_eq!(ON_REMOVE, self.init_component::<OnRemove>());
}
/// Creates a new empty [`World`].
///
/// # Panics
@ -219,15 +236,6 @@ impl World {
&self.bundles
}
/// Creates a [`ComponentInitializer`] for this world.
#[inline]
pub fn component_initializer(&mut self) -> ComponentInitializer {
ComponentInitializer {
components: &mut self.components,
storages: &mut self.storages,
}
}
/// Retrieves this world's [`RemovedComponentEvents`] collection
#[inline]
pub fn removed_components(&self) -> &RemovedComponentEvents {
@ -235,7 +243,7 @@ impl World {
}
/// Creates a new [`Commands`] instance that writes to the world's command queue
/// Use [`World::flush_commands`] to apply all queued commands
/// Use [`World::flush`] to apply all queued commands
#[inline]
pub fn commands(&mut self) -> Commands {
// SAFETY: command_queue is stored on world and always valid while the world exists
@ -502,7 +510,7 @@ impl World {
/// scheme worked out to share an ID space (which doesn't happen by default).
#[inline]
pub fn get_or_spawn(&mut self, entity: Entity) -> Option<EntityWorldMut> {
self.flush_entities();
self.flush();
match self.entities.alloc_at_without_replacement(entity) {
AllocAtWithoutReplacement::Exists(location) => {
// SAFETY: `entity` exists and `location` is that entity's location
@ -895,7 +903,7 @@ impl World {
/// assert_eq!(position.x, 0.0);
/// ```
pub fn spawn_empty(&mut self) -> EntityWorldMut {
self.flush_entities();
self.flush();
let entity = self.entities.alloc();
// SAFETY: entity was just allocated
unsafe { self.spawn_at_empty_internal(entity) }
@ -961,7 +969,7 @@ impl World {
/// assert_eq!(position.x, 2.0);
/// ```
pub fn spawn<B: Bundle>(&mut self, bundle: B) -> EntityWorldMut {
self.flush_entities();
self.flush();
let change_tick = self.change_tick();
let entity = self.entities.alloc();
let entity_location = {
@ -1092,6 +1100,7 @@ impl World {
/// ```
#[inline]
pub fn despawn(&mut self, entity: Entity) -> bool {
self.flush();
if let Some(entity) = self.get_entity_mut(entity) {
entity.despawn();
true
@ -1113,9 +1122,9 @@ impl World {
/// By clearing this internal state, the world "forgets" about those changes, allowing a new round
/// of detection to be recorded.
///
/// When using `bevy_ecs` as part of the full Bevy engine, this method is added as a system to the
/// main app, to run during `Last`, so you don't need to call it manually. When using `bevy_ecs`
/// as a separate standalone crate however, you need to call this manually.
/// When using `bevy_ecs` as part of the full Bevy engine, this method is called automatically
/// by `bevy_app::App::update` and `bevy_app::SubApp::update`, so you don't need to call it manually.
/// When using `bevy_ecs` as a separate standalone crate however, you do need to call this manually.
///
/// ```
/// # use bevy_ecs::prelude::*;
@ -1743,7 +1752,7 @@ impl World {
I::IntoIter: Iterator<Item = (Entity, B)>,
B: Bundle,
{
self.flush_entities();
self.flush();
let change_tick = self.change_tick();
@ -2029,9 +2038,15 @@ impl World {
}
}
/// Calls both [`World::flush_entities`] and [`World::flush_commands`].
#[inline]
pub fn flush(&mut self) {
self.flush_entities();
self.flush_commands();
}
/// Applies any commands in the world's internal [`CommandQueue`].
/// This does not apply commands from any systems, only those stored in the world.
#[inline]
pub fn flush_commands(&mut self) {
// SAFETY: `self.command_queue` is only de-allocated in `World`'s `Drop`
if !unsafe { self.command_queue.is_empty() } {
@ -2082,6 +2097,13 @@ impl World {
self.last_change_tick
}
/// Returns the id of the last ECS event that was fired.
/// Used internally to ensure observers don't trigger multiple times for the same event.
#[inline]
pub(crate) fn last_trigger_id(&self) -> u32 {
self.last_trigger_id
}
/// Sets [`World::last_change_tick()`] to the specified value during a scope.
/// When the scope terminates, it will return to its old value.
///

View file

@ -27,7 +27,7 @@ where
pub(crate) fn new(world: &'w mut World, iter: I) -> Self {
// Ensure all entity allocations are accounted for so `self.entities` can realloc if
// necessary
world.flush_entities();
world.flush();
let change_tick = world.change_tick();

View file

@ -9,6 +9,7 @@ use crate::{
change_detection::{MutUntyped, Ticks, TicksMut},
component::{ComponentId, ComponentTicks, Components, StorageType, Tick, TickCells},
entity::{Entities, Entity, EntityLocation},
observer::Observers,
prelude::Component,
removal_detection::RemovedComponentEvents,
storage::{Column, ComponentSparseSet, Storages},
@ -231,6 +232,13 @@ impl<'w> UnsafeWorldCell<'w> {
&unsafe { self.world_metadata() }.removed_components
}
/// Retrieves this world's [`Observers`] collection.
pub(crate) unsafe fn observers(self) -> &'w Observers {
// SAFETY:
// - we only access world metadata
&unsafe { self.world_metadata() }.observers
}
/// Retrieves this world's [`Bundles`] collection.
#[inline]
pub fn bundles(self) -> &'w Bundles {
@ -571,6 +579,14 @@ impl<'w> UnsafeWorldCell<'w> {
// - caller ensures that we have permission to access the queue
unsafe { (*self.0).command_queue.clone() }
}
/// # Safety
/// It is the callers responsibility to ensure that there are no outstanding
/// references to `last_trigger_id`.
pub(crate) unsafe fn increment_trigger_id(self) {
// SAFETY: Caller ensure there are no outstanding references
unsafe { (*self.0).last_trigger_id += 1 }
}
}
impl Debug for UnsafeWorldCell<'_> {
@ -646,7 +662,7 @@ impl<'w> UnsafeEntityCell<'w> {
///
/// - If you know the concrete type of the component, you should prefer [`Self::contains`].
/// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using
/// [`Self::contains_type_id`].
/// [`Self::contains_type_id`].
#[inline]
pub fn contains_id(self, component_id: ComponentId) -> bool {
self.archetype().contains(component_id)
@ -917,7 +933,7 @@ impl<'w> UnsafeWorldCell<'w> {
///
/// # Safety
/// - `location` must refer to an archetype that contains `entity`
/// the archetype
/// the archetype
/// - `component_id` must be valid
/// - `storage_type` must accurately reflect where the components for `component_id` are stored.
/// - the caller must ensure that no aliasing rules are violated
@ -978,7 +994,7 @@ unsafe fn get_component_and_ticks(
///
/// # Safety
/// - `location` must refer to an archetype that contains `entity`
/// the archetype
/// the archetype
/// - `component_id` must be valid
/// - `storage_type` must accurately reflect where the components for `component_id` are stored.
/// - the caller must ensure that no aliasing rules are violated

View file

@ -23,7 +23,7 @@ where
/// # Arguments
/// - `position` sets the center of this circle.
/// - `direction_angle` sets the counter-clockwise angle in radians between `Vec2::Y` and
/// the vector from `position` to the midpoint of the arc.
/// the vector from `position` to the midpoint of the arc.
/// - `arc_angle` sets the length of this arc, in radians.
/// - `radius` controls the distance from `position` to this arc, and thus its curvature.
/// - `color` sets the color to draw the arc.
@ -154,11 +154,11 @@ where
///
/// # Arguments
/// - `angle`: sets how much of a circle circumference is passed, e.g. PI is half a circle. This
/// value should be in the range (-2 * PI..=2 * PI)
/// value should be in the range (-2 * PI..=2 * PI)
/// - `radius`: distance between the arc and its center point
/// - `position`: position of the arcs center point
/// - `rotation`: defines orientation of the arc, by default we assume the arc is contained in a
/// plane parallel to the XZ plane and the default starting point is (`position + Vec3::X`)
/// plane parallel to the XZ plane and the default starting point is (`position + Vec3::X`)
/// - `color`: color of the arc
///
/// # Builder methods
@ -242,10 +242,10 @@ where
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case
#[inline]
pub fn short_arc_3d_between(
&mut self,
@ -289,10 +289,10 @@ where
///
/// # Notes
/// - This method assumes that the points `from` and `to` are distinct from `center`. If one of
/// the points is coincident with `center`, nothing is rendered.
/// the points is coincident with `center`, nothing is rendered.
/// - The arc is drawn as a portion of a circle with a radius equal to the distance from the
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case.
/// `center` to `from`. If the distance from `center` to `to` is not equal to the radius, then
/// the results will behave as if this were the case.
#[inline]
pub fn long_arc_3d_between(
&mut self,

View file

@ -0,0 +1,77 @@
//! Additional [`Gizmos`] Functions -- Crosses
//!
//! Includes the implementation of [`Gizmos::cross`] and [`Gizmos::cross_2d`],
//! and assorted support items.
use crate::prelude::{GizmoConfigGroup, Gizmos};
use bevy_color::Color;
use bevy_math::{Mat2, Mat3, Quat, Vec2, Vec3};
impl<Config> Gizmos<'_, '_, Config>
where
Config: GizmoConfigGroup,
{
/// Draw a cross in 3D at `position`.
///
/// This should be called for each frame the cross needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::WHITE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cross(Vec3::ZERO, Quat::IDENTITY, 0.5, WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross(
&mut self,
position: Vec3,
rotation: Quat,
half_size: f32,
color: impl Into<Color>,
) {
let axes = half_size * Mat3::from_quat(rotation);
let local_x = axes.col(0);
let local_y = axes.col(1);
let local_z = axes.col(2);
let color: Color = color.into();
self.line(position + local_x, position - local_x, color);
self.line(position + local_y, position - local_y, color);
self.line(position + local_z, position - local_z, color);
}
/// Draw a cross in 2D (on the xy plane) at `position`.
///
/// This should be called for each frame the cross needs to be rendered.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_render::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::palettes::basic::WHITE;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.cross_2d(Vec2::ZERO, 0.0, 0.5, WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn cross_2d(
&mut self,
position: Vec2,
angle: f32,
half_size: f32,
color: impl Into<Color>,
) {
let axes = half_size * Mat2::from_angle(angle);
let local_x = axes.col(0);
let local_y = axes.col(1);
let color: Color = color.into();
self.line_2d(position + local_x, position - local_x, color);
self.line_2d(position + local_y, position - local_y, color);
}
}

View file

@ -1,6 +1,6 @@
//! Additional [`Gizmos`] Functions -- Grids
//!
//! Includes the implementation of[`Gizmos::grid`] and [`Gizmos::grid_2d`].
//! Includes the implementation of [`Gizmos::grid`] and [`Gizmos::grid_2d`].
//! and assorted support items.
use crate::prelude::{GizmoConfigGroup, Gizmos};

View file

@ -36,6 +36,7 @@ pub mod arcs;
pub mod arrows;
pub mod circles;
pub mod config;
pub mod cross;
pub mod gizmos;
pub mod grid;
pub mod primitives;

View file

@ -74,8 +74,8 @@ fn vertex(vertex: VertexInput) -> VertexOutput {
let near_clipping_plane_height = length(pos0.xyz - pos1.xyz);
// We can't use vertex.position_X because we may have changed the clip positions with clip_near_plane
let position_a = view.inverse_clip_from_world * clip_a;
let position_b = view.inverse_clip_from_world * clip_b;
let position_a = view.world_from_clip * clip_a;
let position_b = view.world_from_clip * clip_b;
let world_distance = length(position_a.xyz - position_b.xyz);
// Offset to compensate for moved clip positions. If removed dots on lines will slide when position a is ofscreen.

View file

@ -222,7 +222,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
///
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
@ -278,7 +278,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
///
/// - The corner radius can be adjusted with the `.corner_radius(...)` method.
/// - The resolution of the arcs at each corner (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```
@ -334,7 +334,7 @@ impl<'w, 's, T: GizmoConfigGroup> Gizmos<'w, 's, T> {
///
/// - The edge radius can be adjusted with the `.edge_radius(...)` method.
/// - The resolution of the arcs at each edge (i.e. the level of detail) can be adjusted with the
/// `.arc_resolution(...)` method.
/// `.arc_resolution(...)` method.
///
/// # Example
/// ```

View file

@ -91,6 +91,7 @@ pub trait DespawnRecursiveExt {
impl DespawnRecursiveExt for EntityCommands<'_> {
/// Despawns the provided entity and its children.
/// This will emit warnings for any entity that does not exist.
fn despawn_recursive(mut self) {
let entity = self.id();
self.commands().add(DespawnRecursive { entity });
@ -105,6 +106,7 @@ impl DespawnRecursiveExt for EntityCommands<'_> {
impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> {
/// Despawns the provided entity and its children.
/// This will emit warnings for any entity that does not exist.
fn despawn_recursive(self) {
let entity = self.id();

View file

@ -71,6 +71,7 @@ shader_format_spirv = ["bevy_render/shader_format_spirv"]
serialize = [
"bevy_core/serialize",
"bevy_input/serialize",
"bevy_ecs/serialize",
"bevy_time/serialize",
"bevy_window/serialize",
"bevy_winit?/serialize",
@ -179,6 +180,9 @@ meshlet_processor = ["bevy_pbr?/meshlet_processor"]
# Provides a collection of developer tools
bevy_dev_tools = ["dep:bevy_dev_tools"]
# Provides a picking functionality
bevy_picking = ["dep:bevy_picking"]
# Enable support for the ios_simulator by downgrading some rendering capabilities
ios_simulator = ["bevy_pbr?/ios_simulator", "bevy_render?/ios_simulator"]
@ -213,18 +217,19 @@ bevy_asset = { path = "../bevy_asset", optional = true, version = "0.14.0-dev" }
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.14.0-dev" }
bevy_color = { path = "../bevy_color", optional = true, version = "0.14.0-dev" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.14.0-dev" }
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.14.0-dev" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.14.0-dev" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.14.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.14.0-dev", default-features = false }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.14.0-dev" }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.14.0-dev" }
bevy_picking = { path = "../bevy_picking", optional = true, version = "0.14.0-dev" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.14.0-dev" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.14.0-dev" }
bevy_scene = { path = "../bevy_scene", optional = true, version = "0.14.0-dev" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.14.0-dev" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.14.0-dev" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.14.0-dev" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.14.0-dev" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.14.0-dev" }
bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.14.0-dev", default-features = false }
bevy_dev_tools = { path = "../bevy_dev_tools", optional = true, version = "0.14.0-dev" }
[lints]
workspace = true

View file

@ -28,6 +28,8 @@ use bevy_app::{Plugin, PluginGroup, PluginGroupBuilder};
/// * [`AudioPlugin`](crate::audio::AudioPlugin) - with feature `bevy_audio`
/// * [`GilrsPlugin`](crate::gilrs::GilrsPlugin) - with feature `bevy_gilrs`
/// * [`AnimationPlugin`](crate::animation::AnimationPlugin) - with feature `bevy_animation`
/// * [`GizmoPlugin`](crate::gizmos::GizmoPlugin) - with feature `bevy_gizmos`
/// * [`StatesPlugin`](crate::app::StatesPlugin) - with feature `bevy_state`
/// * [`DevToolsPlugin`](crate::dev_tools::DevToolsPlugin) - with feature `bevy_dev_tools`
/// * [`CiTestingPlugin`](crate::dev_tools::ci_testing::CiTestingPlugin) - with feature `bevy_ci_testing`
///

View file

@ -44,6 +44,8 @@ pub use bevy_log as log;
pub use bevy_math as math;
#[cfg(feature = "bevy_pbr")]
pub use bevy_pbr as pbr;
#[cfg(feature = "bevy_picking")]
pub use bevy_picking as picking;
pub use bevy_ptr as ptr;
pub use bevy_reflect as reflect;
#[cfg(feature = "bevy_render")]

View file

@ -66,11 +66,11 @@ pub(crate) struct FlushGuard(SyncCell<tracing_chrome::FlushGuard>);
/// Adds logging to Apps. This plugin is part of the `DefaultPlugins`. Adding
/// this plugin will setup a collector appropriate to your target platform:
/// * Using [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) by default,
/// logging to `stdout`.
/// logging to `stdout`.
/// * Using [`android_log-sys`](https://crates.io/crates/android_log-sys) on Android,
/// logging to Android logs.
/// logging to Android logs.
/// * Using [`tracing-wasm`](https://crates.io/crates/tracing-wasm) in WASM, logging
/// to the browser console.
/// to the browser console.
///
/// You can configure this plugin.
/// ```no_run

View file

@ -1,4 +1,4 @@
use glam::{Vec2, Vec3, Vec3A, Vec4};
use crate::{Dir2, Dir3, Dir3A, Quat, Rot2, Vec2, Vec3, Vec3A, Vec4};
use std::fmt::Debug;
use std::ops::{Add, Div, Mul, Neg, Sub};
@ -161,3 +161,147 @@ impl NormedVectorSpace for f32 {
self * self
}
}
/// A type with a natural interpolation that provides strong subdivision guarantees.
///
/// Although the only required method is `interpolate_stable`, many things are expected of it:
///
/// 1. The notion of interpolation should follow naturally from the semantics of the type, so
/// that inferring the interpolation mode from the type alone is sensible.
///
/// 2. The interpolation recovers something equivalent to the starting value at `t = 0.0`
/// and likewise with the ending value at `t = 1.0`. They do not have to be data-identical, but
/// they should be semantically identical. For example, [`Quat::slerp`] doesn't always yield its
/// second rotation input exactly at `t = 1.0`, but it always returns an equivalent rotation.
///
/// 3. Importantly, the interpolation must be *subdivision-stable*: for any interpolation curve
/// between two (unnamed) values and any parameter-value pairs `(t0, p)` and `(t1, q)`, the
/// interpolation curve between `p` and `q` must be the *linear* reparametrization of the original
/// interpolation curve restricted to the interval `[t0, t1]`.
///
/// The last of these conditions is very strong and indicates something like constant speed. It
/// is called "subdivision stability" because it guarantees that breaking up the interpolation
/// into segments and joining them back together has no effect.
///
/// Here is a diagram depicting it:
/// ```text
/// top curve = u.interpolate_stable(v, t)
///
/// t0 => p t1 => q
/// |-------------|---------|-------------|
/// 0 => u / \ 1 => v
/// / \
/// / \
/// / linear \
/// / reparametrization \
/// / t = t0 * (1 - s) + t1 * s \
/// / \
/// |-------------------------------------|
/// 0 => p 1 => q
///
/// bottom curve = p.interpolate_stable(q, s)
/// ```
///
/// Note that some common forms of interpolation do not satisfy this criterion. For example,
/// [`Quat::lerp`] and [`Rot2::nlerp`] are not subdivision-stable.
///
/// Furthermore, this is not to be used as a general trait for abstract interpolation.
/// Consumers rely on the strong guarantees in order for behavior based on this trait to be
/// well-behaved.
///
/// [`Quat::slerp`]: crate::Quat::slerp
/// [`Quat::lerp`]: crate::Quat::lerp
/// [`Rot2::nlerp`]: crate::Rot2::nlerp
pub trait StableInterpolate: Clone {
/// Interpolate between this value and the `other` given value using the parameter `t`. At
/// `t = 0.0`, a value equivalent to `self` is recovered, while `t = 1.0` recovers a value
/// equivalent to `other`, with intermediate values interpolating between the two.
/// See the [trait-level documentation] for details.
///
/// [trait-level documentation]: StableInterpolate
fn interpolate_stable(&self, other: &Self, t: f32) -> Self;
/// A version of [`interpolate_stable`] that assigns the result to `self` for convenience.
///
/// [`interpolate_stable`]: StableInterpolate::interpolate_stable
fn interpolate_stable_assign(&mut self, other: &Self, t: f32) {
*self = self.interpolate_stable(other, t);
}
/// Smoothly nudge this value towards the `target` at a given decay rate. The `decay_rate`
/// parameter controls how fast the distance between `self` and `target` decays relative to
/// the units of `delta`; the intended usage is for `decay_rate` to generally remain fixed,
/// while `delta` is something like `delta_time` from an updating system. This produces a
/// smooth following of the target that is independent of framerate.
///
/// More specifically, when this is called repeatedly, the result is that the distance between
/// `self` and a fixed `target` attenuates exponentially, with the rate of this exponential
/// decay given by `decay_rate`.
///
/// For example, at `decay_rate = 0.0`, this has no effect.
/// At `decay_rate = f32::INFINITY`, `self` immediately snaps to `target`.
/// In general, higher rates mean that `self` moves more quickly towards `target`.
///
/// # Example
/// ```
/// # use bevy_math::{Vec3, StableInterpolate};
/// # let delta_time: f32 = 1.0 / 60.0;
/// let mut object_position: Vec3 = Vec3::ZERO;
/// let target_position: Vec3 = Vec3::new(2.0, 3.0, 5.0);
/// // Decay rate of ln(10) => after 1 second, remaining distance is 1/10th
/// let decay_rate = f32::ln(10.0);
/// // Calling this repeatedly will move `object_position` towards `target_position`:
/// object_position.smooth_nudge(&target_position, decay_rate, delta_time);
/// ```
fn smooth_nudge(&mut self, target: &Self, decay_rate: f32, delta: f32) {
self.interpolate_stable_assign(target, 1.0 - f32::exp(-decay_rate * delta));
}
}
// Conservatively, we presently only apply this for normed vector spaces, where the notion
// of being constant-speed is literally true. The technical axioms are satisfied for any
// VectorSpace type, but the "natural from the semantics" part is less clear in general.
impl<V> StableInterpolate for V
where
V: NormedVectorSpace,
{
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.lerp(*other, t)
}
}
impl StableInterpolate for Rot2 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Quat {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir2 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir3 {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}
impl StableInterpolate for Dir3A {
#[inline]
fn interpolate_stable(&self, other: &Self, t: f32) -> Self {
self.slerp(*other, t)
}
}

View file

@ -5,6 +5,17 @@ use bevy_reflect::Reflect;
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
/// A compass enum with 4 directions.
/// ``` ignore
/// N (North)
/// ▲
/// │
/// │
/// W (West) ┼─────► E (East)
/// │
/// │
/// ▼
/// S (South)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]
@ -24,6 +35,17 @@ pub enum CompassQuadrant {
}
/// A compass enum with 8 directions.
/// ``` ignore
/// N (North)
/// ▲
/// NW │ NE
/// ╲ │
/// W (West) ┼─────► E (East)
/// │ ╲
/// SW │ SE
/// ▼
/// S (South)
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Debug, PartialEq))]

View file

@ -53,8 +53,8 @@ pub mod prelude {
direction::{Dir2, Dir3, Dir3A},
primitives::*,
BVec2, BVec3, BVec4, EulerRot, FloatExt, IRect, IVec2, IVec3, IVec4, Mat2, Mat3, Mat4,
Quat, Ray2d, Ray3d, Rect, Rot2, URect, UVec2, UVec3, UVec4, Vec2, Vec2Swizzles, Vec3,
Vec3Swizzles, Vec4, Vec4Swizzles,
Quat, Ray2d, Ray3d, Rect, Rot2, StableInterpolate, URect, UVec2, UVec3, UVec4, Vec2,
Vec2Swizzles, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles,
};
}

View file

@ -1,4 +1,4 @@
use bevy_color::{Color, LinearRgba};
use bevy_color::{Color, ColorToComponents, LinearRgba};
use bevy_ecs::prelude::*;
use bevy_math::Vec3;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
@ -144,11 +144,11 @@ pub enum FogFalloff {
/// ## Tips
///
/// - Use the [`FogFalloff::from_visibility()`] convenience method to create an exponential falloff with the proper
/// density for a desired visibility distance in world units;
/// density for a desired visibility distance in world units;
/// - It's not _unusual_ to have very large or very small values for the density, depending on the scene
/// scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values
/// in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of
/// density;
/// scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values
/// in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of
/// density;
/// - Combine the `density` parameter with the [`FogSettings`] `color`'s alpha channel for easier artistic control.
///
/// ## Formula
@ -196,7 +196,7 @@ pub enum FogFalloff {
/// ## Tips
///
/// - Use the [`FogFalloff::from_visibility_squared()`] convenience method to create an exponential squared falloff
/// with the proper density for a desired visibility distance in world units;
/// with the proper density for a desired visibility distance in world units;
/// - Combine the `density` parameter with the [`FogSettings`] `color`'s alpha channel for easier artistic control.
///
/// ## Formula
@ -242,8 +242,8 @@ pub enum FogFalloff {
/// ## Tips
///
/// - Use the [`FogFalloff::from_visibility_colors()`] or [`FogFalloff::from_visibility_color()`] convenience methods
/// to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and
/// extinction and inscattering colors;
/// to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and
/// extinction and inscattering colors;
/// - Combine the atmospheric fog parameters with the [`FogSettings`] `color`'s alpha channel for easier artistic control.
///
/// ## Formula

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