From 1175cf79203a05c26a56978ec2128ce0f1ce9771 Mon Sep 17 00:00:00 2001 From: JohnTheCoolingFan <43478602+JohnTheCoolingFan@users.noreply.github.com> Date: Sat, 28 Sep 2024 19:26:00 +0300 Subject: [PATCH 1/6] Fix `ReflectKind` description wording (#15498) # Objective The "zero-sized" description was outdated and misleading. ## Solution Changed the description to just say that it's an enumeration (an enum) --- crates/bevy_reflect/src/kind.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_reflect/src/kind.rs b/crates/bevy_reflect/src/kind.rs index 114be8ed3830c..0cb2f037c2ea1 100644 --- a/crates/bevy_reflect/src/kind.rs +++ b/crates/bevy_reflect/src/kind.rs @@ -1,9 +1,10 @@ +use thiserror::Error; + #[cfg(feature = "functions")] use crate::func::Function; use crate::{Array, Enum, List, Map, PartialReflect, Set, Struct, Tuple, TupleStruct}; -use thiserror::Error; -/// A zero-sized enumeration of the "kinds" of a reflected type. +/// An enumeration of the "kinds" of a reflected type. /// /// Each kind corresponds to a specific reflection trait, /// such as [`Struct`] or [`List`], @@ -272,9 +273,10 @@ impl ReflectOwned { #[cfg(test)] mod tests { - use super::*; use std::collections::HashSet; + use super::*; + #[test] fn should_cast_ref() { let value = vec![1, 2, 3]; From 7ee5143d45f9246f9cffc52e916ae12d85a71779 Mon Sep 17 00:00:00 2001 From: hshrimp <182684536+hooded-shrimp@users.noreply.github.com> Date: Sat, 28 Sep 2024 09:26:55 -0700 Subject: [PATCH 2/6] Remove `Return::Unit` variant (#15484) # Objective - Fixes #15447 ## Solution - Remove the `Return::Unit` variant and use a `Return::Owned` variant holding a unit `()` type. ## Migration Guide - Removed the `Return::Unit` variant; use `Return::unit()` instead. --------- Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com> --- crates/bevy_reflect/src/func/return_type.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/bevy_reflect/src/func/return_type.rs b/crates/bevy_reflect/src/func/return_type.rs index 9ad44018d93b2..124ce4d7356a9 100644 --- a/crates/bevy_reflect/src/func/return_type.rs +++ b/crates/bevy_reflect/src/func/return_type.rs @@ -6,9 +6,9 @@ use crate::PartialReflect; /// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut #[derive(Debug)] pub enum Return<'a> { - /// The function returns nothing (i.e. it returns `()`). - Unit, /// The function returns an owned value. + /// + /// This includes functions that return nothing (i.e. they return `()`). Owned(Box), /// The function returns a reference to a value. Ref(&'a dyn PartialReflect), @@ -17,9 +17,17 @@ pub enum Return<'a> { } impl<'a> Return<'a> { - /// Returns `true` if the return value is [`Self::Unit`]. + /// Creates an [`Owned`](Self::Owned) unit (`()`) type. + pub fn unit() -> Self { + Self::Owned(Box::new(())) + } + + /// Returns `true` if the return value is an [`Owned`](Self::Owned) unit (`()`) type. pub fn is_unit(&self) -> bool { - matches!(self, Return::Unit) + match self { + Return::Owned(val) => val.represents::<()>(), + _ => false, + } } /// Unwraps the return value as an owned value. @@ -81,7 +89,7 @@ pub trait IntoReturn { impl IntoReturn for () { fn into_return<'a>(self) -> Return<'a> { - Return::Unit + Return::unit() } } From 29edad46905d4a59b14674fc875f3858a743e31b Mon Sep 17 00:00:00 2001 From: Dokkae <90514461+Dokkae6949@users.noreply.github.com> Date: Sat, 28 Sep 2024 21:13:27 +0200 Subject: [PATCH 3/6] Improve unclear docs about spawn(_batch) and ParallelCommands (#15491) > [!NOTE] > This is my first PR, so if something is incorrect > or missing, please let me know :3 # Objective - Clarifies `spawn`, `spawn_batch` and `ParallelCommands` docs about performance and use cases - Fixes #15472 ## Solution Add comments to `spawn`, `spawn_batch` and `ParallelCommands` to clarify the intended use case and link to other/better ways of doing spawning things for certain use cases. --- crates/bevy_ecs/src/system/commands/mod.rs | 3 +++ crates/bevy_ecs/src/system/commands/parallel_scope.rs | 7 ++++++- crates/bevy_ecs/src/world/mod.rs | 9 +++++---- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 1476f32db1972..fca9e8f94bcc9 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -338,6 +338,9 @@ impl<'w, 's> Commands<'w, 's> { /// Pushes a [`Command`] to the queue for creating a new entity with the given [`Bundle`]'s components, /// and returns its corresponding [`EntityCommands`]. /// + /// In case multiple bundles of the same [`Bundle`] type need to be spawned, + /// [`spawn_batch`](Self::spawn_batch) should be used for better performance. + /// /// # Example /// /// ``` diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 582825aa794da..82a00ceac2977 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -14,7 +14,12 @@ struct ParallelCommandQueue { thread_queues: Parallel, } -/// An alternative to [`Commands`] that can be used in parallel contexts, such as those in [`Query::par_iter`](crate::system::Query::par_iter) +/// An alternative to [`Commands`] that can be used in parallel contexts, such as those +/// in [`Query::par_iter`](crate::system::Query::par_iter). +/// +/// For cases where multiple non-computation-heavy (lightweight) bundles of the same +/// [`Bundle`](crate::prelude::Bundle) type need to be spawned, consider using +/// [`Commands::spawn_batch`] for better performance. /// /// Note: Because command application order will depend on how many threads are ran, non-commutative commands may result in non-deterministic results. /// diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 7e0d194442fd5..80a05e2a18f76 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -921,7 +921,8 @@ impl World { /// Spawns a new [`Entity`] with a given [`Bundle`] of [components](`Component`) and returns /// a corresponding [`EntityWorldMut`], which can be used to add components to the entity or - /// retrieve its id. + /// retrieve its id. In case large batches of entities need to be spawned, consider using + /// [`World::spawn_batch`] instead. /// /// ``` /// use bevy_ecs::{bundle::Bundle, component::Component, world::World}; @@ -1018,9 +1019,9 @@ impl World { /// Spawns a batch of entities with the same component [`Bundle`] type. Takes a given /// [`Bundle`] iterator and returns a corresponding [`Entity`] iterator. - /// This is more efficient than spawning entities and adding components to them individually, - /// but it is limited to spawning entities with the same [`Bundle`] type, whereas spawning - /// individually is more flexible. + /// This is more efficient than spawning entities and adding components to them individually + /// using [`World::spawn`], but it is limited to spawning entities with the same [`Bundle`] + /// type, whereas spawning individually is more flexible. /// /// ``` /// use bevy_ecs::{component::Component, entity::Entity, world::World}; From 89925ee3511b0b97c3218f059ea88d2fee0150ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Mockers?= Date: Sat, 28 Sep 2024 21:21:59 +0200 Subject: [PATCH 4/6] bump async-channel to 2.3.0 (#15497) # Objective - We use a feature introduced in async-channel 2.3.0, `force_send` - Existing project fail to compile as they have a lock file on the 2.2.X ## Solution - Bump async-channel --- crates/bevy_render/Cargo.toml | 2 +- crates/bevy_tasks/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 812e389ff7908..41071526800f1 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -100,7 +100,7 @@ encase = { version = "0.10", features = ["glam"] } profiling = { version = "1", features = [ "profile-with-tracing", ], optional = true } -async-channel = "2.2.0" +async-channel = "2.3.0" nonmax = "0.5" smallvec = { version = "1.11", features = ["const_new"] } offset-allocator = "0.2" diff --git a/crates/bevy_tasks/Cargo.toml b/crates/bevy_tasks/Cargo.toml index 0e8831e59f9c9..e915cd941f57f 100644 --- a/crates/bevy_tasks/Cargo.toml +++ b/crates/bevy_tasks/Cargo.toml @@ -14,7 +14,7 @@ multi_threaded = ["dep:async-channel", "dep:concurrent-queue"] [dependencies] futures-lite = "2.0.1" async-executor = "1.11" -async-channel = { version = "2.2.0", optional = true } +async-channel = { version = "2.3.0", optional = true } async-io = { version = "2.0.0", optional = true } concurrent-queue = { version = "2.0.0", optional = true } From c1486654d70968e51480ddbfa2c28600b6da9886 Mon Sep 17 00:00:00 2001 From: MiniaczQ Date: Sat, 28 Sep 2024 21:35:27 +0200 Subject: [PATCH 5/6] `QuerySingle` family of system params (#15476) # Objective Add the following system params: - `QuerySingle` - Valid if only one matching entity exists, - `Option>` - Valid if zero or one matching entity exists. As @chescock pointed out, we don't need `Mut` variants. Fixes: #15264 ## Solution Implement the type and both variants of system params. Also implement `ReadOnlySystemParam` for readonly queries. Added a new ECS example `fallible_params` which showcases `SingleQuery` usage. In the future we might want to add `NonEmptyQuery`, `NonEmptyEventReader` and `Res` to it (or maybe just stop at mentioning it). ## Testing Tested with the example. There is a lot of warning spam so we might want to implement #15391. --- Cargo.toml | 11 ++ crates/bevy_ecs/src/change_detection.rs | 21 ++- crates/bevy_ecs/src/lib.rs | 5 +- crates/bevy_ecs/src/system/query.rs | 40 +++++- crates/bevy_ecs/src/system/system.rs | 4 + crates/bevy_ecs/src/system/system_param.rs | 156 ++++++++++++++++++++- examples/README.md | 1 + examples/ecs/fallible_params.rs | 147 +++++++++++++++++++ 8 files changed, 363 insertions(+), 22 deletions(-) create mode 100644 examples/ecs/fallible_params.rs diff --git a/Cargo.toml b/Cargo.toml index 6fdb2b37f4989..7a1f5fb78b97d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1892,6 +1892,17 @@ description = "Run systems only when one or multiple conditions are met" category = "ECS (Entity Component System)" wasm = false +[[example]] +name = "fallible_params" +path = "examples/ecs/fallible_params.rs" +doc-scrape-examples = true + +[package.metadata.example.fallible_params] +name = "Fallible System Parameters" +description = "Systems are skipped if their parameters cannot be acquired" +category = "ECS (Entity Component System)" +wasm = false + [[example]] name = "startup_system" path = "examples/ecs/startup_system.rs" diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 2c7c6615fce4c..5a3adac96fe20 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -543,11 +543,10 @@ impl<'w> From> for Ticks<'w> { /// /// If you need a unique mutable borrow, use [`ResMut`] instead. /// -/// # Panics +/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist. +/// This will cause systems that use this parameter to be skipped. /// -/// Panics when used as a [`SystemParameter`](crate::system::SystemParam) if the resource does not exist. -/// -/// Use `Option>` instead if the resource might not always exist. +/// Use [`Option>`] instead if the resource might not always exist. pub struct Res<'w, T: ?Sized + Resource> { pub(crate) value: &'w T, pub(crate) ticks: Ticks<'w>, @@ -622,11 +621,10 @@ impl_debug!(Res<'w, T>, Resource); /// /// If you need a shared borrow, use [`Res`] instead. /// -/// # Panics -/// -/// Panics when used as a [`SystemParam`](crate::system::SystemParam) if the resource does not exist. +/// This [`SystemParam`](crate::system::SystemParam) fails validation if resource doesn't exist. +/// This will cause systems that use this parameter to be skipped. /// -/// Use `Option>` instead if the resource might not always exist. +/// Use [`Option>`] instead if the resource might not always exist. pub struct ResMut<'w, T: ?Sized + Resource> { pub(crate) value: &'w mut T, pub(crate) ticks: TicksMut<'w>, @@ -684,11 +682,10 @@ impl<'w, T: Resource> From> for Mut<'w, T> { /// the scheduler to instead run the system on the main thread so that it doesn't send the resource /// over to another thread. /// -/// # Panics -/// -/// Panics when used as a `SystemParameter` if the resource does not exist. +/// This [`SystemParam`](crate::system::SystemParam) fails validation if non-send resource doesn't exist. +/// This will cause systems that use this parameter to be skipped. /// -/// Use `Option>` instead if the resource might not always exist. +/// Use [`Option>`] instead if the resource might not always exist. pub struct NonSendMut<'w, T: ?Sized + 'static> { pub(crate) value: &'w mut T, pub(crate) ticks: TicksMut<'w>, diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 69dfea19f5bda..c7d49dfa3a74e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -58,8 +58,9 @@ pub mod prelude { }, system::{ Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local, - NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut, - Resource, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction, + NonSend, NonSendMut, ParallelCommands, ParamSet, Query, QuerySingle, ReadOnlySystem, + Res, ResMut, Resource, System, SystemIn, SystemInput, SystemParamBuilder, + SystemParamFunction, }, world::{ Command, EntityMut, EntityRef, EntityWorldMut, FromWorld, OnAdd, OnInsert, OnRemove, diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 83681ae5b686f..13155057ff91b 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -8,7 +8,11 @@ use crate::{ }, world::unsafe_world_cell::UnsafeWorldCell, }; -use core::borrow::Borrow; +use core::{ + borrow::Borrow, + marker::PhantomData, + ops::{Deref, DerefMut}, +}; /// [System parameter] that provides selective access to the [`Component`] data stored in a [`World`]. /// @@ -1629,3 +1633,37 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> value.transmute_lens_filtered() } } + +/// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`]. +/// +/// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists. +/// This will cause systems that use this parameter to be skipped. +/// +/// Use [`Option>`] instead if zero or one matching entities can exist. +/// +/// See [`Query`] for more details. +pub struct QuerySingle<'w, D: QueryData, F: QueryFilter = ()> { + pub(crate) item: D::Item<'w>, + pub(crate) _filter: PhantomData, +} + +impl<'w, D: QueryData, F: QueryFilter> Deref for QuerySingle<'w, D, F> { + type Target = D::Item<'w>; + + fn deref(&self) -> &Self::Target { + &self.item + } +} + +impl<'w, D: QueryData, F: QueryFilter> DerefMut for QuerySingle<'w, D, F> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.item + } +} + +impl<'w, D: QueryData, F: QueryFilter> QuerySingle<'w, D, F> { + /// Returns the inner item with ownership. + pub fn into_inner(self) -> D::Item<'w> { + self.item + } +} diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 63ef1a16c3d17..7741f3cb80746 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -104,6 +104,10 @@ pub trait System: Send + Sync + 'static { /// is not a strict requirement, both [`System::run`] and [`System::run_unsafe`] /// should provide their own safety mechanism to prevent undefined behavior. /// + /// This method has to be called directly before [`System::run_unsafe`] with no other (relevant) + /// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior, + /// the validity of the param may change. + /// /// # Safety /// /// - The caller must ensure that [`world`](UnsafeWorldCell) has permission to access any world data diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index ba959bb86aca7..fdcf36a018ba3 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -7,10 +7,10 @@ use crate::{ entity::Entities, query::{ Access, AccessConflicts, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, - QueryState, ReadOnlyQueryData, + QuerySingleError, QueryState, ReadOnlyQueryData, }, storage::{ResourceData, SparseSetIndex}, - system::{Query, SystemMeta}, + system::{Query, QuerySingle, SystemMeta}, world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FromWorld, World}, }; use bevy_ecs_macros::impl_param_set; @@ -227,12 +227,21 @@ pub unsafe trait SystemParam: Sized { /// The [`world`](UnsafeWorldCell) can only be used to read param's data /// and world metadata. No data can be written. /// + /// When using system parameters that require `change_tick` you can use + /// [`UnsafeWorldCell::change_tick()`]. Even if this isn't the exact + /// same tick used for [`SystemParam::get_param`], the world access + /// ensures that the queried data will be the same in both calls. + /// + /// This method has to be called directly before [`SystemParam::get_param`] with no other (relevant) + /// world mutations inbetween. Otherwise, while it won't lead to any undefined behavior, + /// the validity of the param may change. + /// /// # Safety /// /// - The passed [`UnsafeWorldCell`] must have read-only access to world data /// registered in [`init_state`](SystemParam::init_state). /// - `world` must be the same [`World`] that was used to initialize [`state`](SystemParam::init_state). - /// - all `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype). + /// - All `world`'s archetypes have been processed by [`new_archetype`](SystemParam::new_archetype). unsafe fn validate_param( _state: &Self::State, _system_meta: &SystemMeta, @@ -356,6 +365,140 @@ fn assert_component_access_compatibility( panic!("error[B0001]: Query<{query_type}, {filter_type}> in system {system_name} accesses component(s){accesses} in a way that conflicts with a previous system parameter. Consider using `Without` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/b0001"); } +// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If +// this Query conflicts with any prior access, a panic will occur. +unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam + for QuerySingle<'a, D, F> +{ + type State = QueryState; + type Item<'w, 's> = QuerySingle<'w, D, F>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + Query::init_state(world, system_meta) + } + + unsafe fn new_archetype( + state: &mut Self::State, + archetype: &Archetype, + system_meta: &mut SystemMeta, + ) { + // SAFETY: Delegate to existing `SystemParam` implementations. + unsafe { Query::new_archetype(state, archetype, system_meta) }; + } + + #[inline] + unsafe fn get_param<'w, 's>( + state: &'s mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + change_tick: Tick, + ) -> Self::Item<'w, 's> { + state.validate_world(world.id()); + // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. + let result = + unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) }; + let single = + result.expect("The query was expected to contain exactly one matching entity."); + QuerySingle { + item: single, + _filter: PhantomData, + } + } + + #[inline] + unsafe fn validate_param( + state: &Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> bool { + state.validate_world(world.id()); + // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere + // and the query is read only. + let result = unsafe { + state.as_readonly().get_single_unchecked_manual( + world, + system_meta.last_run, + world.change_tick(), + ) + }; + result.is_ok() + } +} + +// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If +// this Query conflicts with any prior access, a panic will occur. +unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam + for Option> +{ + type State = QueryState; + type Item<'w, 's> = Option>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + QuerySingle::init_state(world, system_meta) + } + + unsafe fn new_archetype( + state: &mut Self::State, + archetype: &Archetype, + system_meta: &mut SystemMeta, + ) { + // SAFETY: Delegate to existing `SystemParam` implementations. + unsafe { QuerySingle::new_archetype(state, archetype, system_meta) }; + } + + #[inline] + unsafe fn get_param<'w, 's>( + state: &'s mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + change_tick: Tick, + ) -> Self::Item<'w, 's> { + state.validate_world(world.id()); + // SAFETY: State ensures that the components it accesses are not accessible elsewhere. + let result = + unsafe { state.get_single_unchecked_manual(world, system_meta.last_run, change_tick) }; + match result { + Ok(single) => Some(QuerySingle { + item: single, + _filter: PhantomData, + }), + Err(QuerySingleError::NoEntities(_)) => None, + Err(QuerySingleError::MultipleEntities(e)) => panic!("{}", e), + } + } + + #[inline] + unsafe fn validate_param( + state: &Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> bool { + state.validate_world(world.id()); + // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere + // and the query is read only. + let result = unsafe { + state.as_readonly().get_single_unchecked_manual( + world, + system_meta.last_run, + world.change_tick(), + ) + }; + !matches!(result, Err(QuerySingleError::MultipleEntities(_))) + } +} + +// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. +unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam + for QuerySingle<'a, D, F> +{ +} + +// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. +unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam + for Option> +{ +} + /// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access. /// /// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as @@ -1172,11 +1315,10 @@ unsafe impl SystemParam for Deferred<'_, T> { /// the scheduler to instead run the system on the main thread so that it doesn't send the resource /// over to another thread. /// -/// # Panics -/// -/// Panics when used as a `SystemParameter` if the resource does not exist. +/// This [`SystemParam`] fails validation if non-send resource doesn't exist. +/// This will cause systems that use this parameter to be skipped. /// -/// Use `Option>` instead if the resource might not always exist. +/// Use [`Option>`] instead if the resource might not always exist. pub struct NonSend<'w, T: 'static> { pub(crate) value: &'w T, ticks: ComponentTicks, diff --git a/examples/README.md b/examples/README.md index 616e37648aed0..96b7c577a4417 100644 --- a/examples/README.md +++ b/examples/README.md @@ -288,6 +288,7 @@ Example | Description [Dynamic ECS](../examples/ecs/dynamic.rs) | Dynamically create components, spawn entities with those components and query those components [ECS Guide](../examples/ecs/ecs_guide.rs) | Full guide to Bevy's ECS [Event](../examples/ecs/event.rs) | Illustrates event creation, activation, and reception +[Fallible System Parameters](../examples/ecs/fallible_params.rs) | Systems are skipped if their parameters cannot be acquired [Fixed Timestep](../examples/ecs/fixed_timestep.rs) | Shows how to create systems that run every fixed timestep, rather than every tick [Generic System](../examples/ecs/generic_system.rs) | Shows how to create systems that can be reused with different types [Hierarchy](../examples/ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities diff --git a/examples/ecs/fallible_params.rs b/examples/ecs/fallible_params.rs new file mode 100644 index 0000000000000..6965fbcf1d321 --- /dev/null +++ b/examples/ecs/fallible_params.rs @@ -0,0 +1,147 @@ +//! This example demonstrates how fallible parameters can prevent their systems +//! from running if their acquiry conditions aren't met. +//! +//! Fallible parameters include: +//! - [`Res`], [`ResMut`] - If resource doesn't exist. +//! - [`QuerySingle`] - If there is no or more than one entities matching. +//! - [`Option>`] - If there are more than one entities matching. + +use bevy::prelude::*; +use rand::Rng; + +fn main() { + println!(); + println!("Press 'A' to add enemy ships and 'R' to remove them."); + println!("Player ship will wait for enemy ships and track one if it exists,"); + println!("but will stop tracking if there are more than one."); + println!(); + + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + // We add all the systems one after another. + // We don't need to use run conditions here. + .add_systems(Update, (user_input, move_targets, move_pointer).chain()) + .run(); +} + +/// Enemy component stores data for movement in a circle. +#[derive(Component, Default)] +struct Enemy { + origin: Vec2, + radius: f32, + rotation: f32, + rotation_speed: f32, +} + +/// Player component stores data for going after enemies. +#[derive(Component, Default)] +struct Player { + speed: f32, + rotation_speed: f32, + min_follow_radius: f32, +} + +fn setup(mut commands: Commands, asset_server: Res) { + // Spawn 2D camera. + commands.spawn(Camera2dBundle::default()); + + // Spawn player. + let texture = asset_server.load("textures/simplespace/ship_C.png"); + commands.spawn(( + Player { + speed: 100.0, + rotation_speed: 2.0, + min_follow_radius: 50.0, + }, + SpriteBundle { + sprite: Sprite { + color: bevy::color::palettes::tailwind::BLUE_800.into(), + ..default() + }, + transform: Transform::from_translation(Vec3::ZERO), + texture, + ..default() + }, + )); +} + +// System that reads user input. +// If user presses 'A' we spawn a new random enemy. +// If user presses 'R' we remove a random enemy (if any exist). +fn user_input( + mut commands: Commands, + enemies: Query>, + keyboard_input: Res>, + asset_server: Res, +) { + let mut rng = rand::thread_rng(); + if keyboard_input.just_pressed(KeyCode::KeyA) { + let texture = asset_server.load("textures/simplespace/enemy_A.png"); + commands.spawn(( + Enemy { + origin: Vec2::new(rng.gen_range(-200.0..200.0), rng.gen_range(-200.0..200.0)), + radius: rng.gen_range(50.0..150.0), + rotation: rng.gen_range(0.0..std::f32::consts::TAU), + rotation_speed: rng.gen_range(0.5..1.5), + }, + SpriteBundle { + sprite: Sprite { + color: bevy::color::palettes::tailwind::RED_800.into(), + ..default() + }, + transform: Transform::from_translation(Vec3::ZERO), + texture, + ..default() + }, + )); + } + + if keyboard_input.just_pressed(KeyCode::KeyR) { + if let Some(entity) = enemies.iter().next() { + commands.entity(entity).despawn(); + } + } +} + +// System that moves the enemies in a circle. +// TODO: Use [`NonEmptyQuery`] when it exists. +fn move_targets(mut enemies: Query<(&mut Transform, &mut Enemy)>, time: Res