mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Merge branch 'main' into new-example-multiple-sprites-one-entity
This commit is contained in:
commit
863b0c913e
207 changed files with 8363 additions and 2596 deletions
|
@ -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 --"
|
||||
|
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -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: |
|
||||
|
|
4
.github/workflows/validation-jobs.yml
vendored
4
.github/workflows/validation-jobs.yml
vendored
|
@ -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
|
||||
|
|
59
Cargo.toml
59
Cargo.toml
|
@ -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"
|
||||
|
|
|
@ -49,7 +49,6 @@ fn fragment(
|
|||
double_sided,
|
||||
is_front,
|
||||
Nt,
|
||||
view.mip_bias,
|
||||
);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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`]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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>);
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
///
|
||||
|
|
|
@ -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<()>>,
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),)*
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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
107
crates/bevy_ecs/src/event/base.rs
Normal file
107
crates/bevy_ecs/src/event/base.rs
Normal 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,
|
||||
}
|
406
crates/bevy_ecs/src/event/collections.rs
Normal file
406
crates/bevy_ecs/src/event/collections.rs
Normal 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);
|
||||
}
|
||||
}
|
268
crates/bevy_ecs/src/event/iterators.rs
Normal file
268
crates/bevy_ecs/src/event/iterators.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
450
crates/bevy_ecs/src/event/mod.rs
Normal file
450
crates/bevy_ecs/src/event/mod.rs
Normal 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
|
||||
}
|
224
crates/bevy_ecs/src/event/reader.rs
Normal file
224
crates/bevy_ecs/src/event/reader.rs
Normal 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;
|
||||
}
|
||||
}
|
80
crates/bevy_ecs/src/event/registry.rs
Normal file
80
crates/bevy_ecs/src/event/registry.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
62
crates/bevy_ecs/src/event/update.rs
Normal file
62
crates/bevy_ecs/src/event/update.rs
Normal 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,
|
||||
}
|
||||
}
|
95
crates/bevy_ecs/src/event/writer.rs
Normal file
95
crates/bevy_ecs/src/event/writer.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
42
crates/bevy_ecs/src/observer/entity_observer.rs
Normal file
42
crates/bevy_ecs/src/observer/entity_observer.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
658
crates/bevy_ecs/src/observer/mod.rs
Normal file
658
crates/bevy_ecs/src/observer/mod.rs
Normal 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);
|
||||
}
|
||||
}
|
409
crates/bevy_ecs/src/observer/runner.rs
Normal file
409
crates/bevy_ecs/src/observer/runner.rs
Normal 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());
|
||||
}
|
||||
}
|
165
crates/bevy_ecs/src/observer/trigger_event.rs
Normal file
165
crates/bevy_ecs/src/observer/trigger_event.rs
Normal 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()
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
///
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
71
crates/bevy_ecs/src/system/observer_system.rs
Normal file
71
crates/bevy_ecs/src/system/observer_system.rs
Normal 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);
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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.
|
||||
|
|
23
crates/bevy_ecs/src/world/component_constants.rs
Normal file
23
crates/bevy_ecs/src/world/component_constants.rs
Normal 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;
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
77
crates/bevy_gizmos/src/cross.rs
Normal file
77
crates/bevy_gizmos/src/cross.rs
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
/// ```
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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`
|
||||
///
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue