Skip to content

Commit

Permalink
Merge branch 'bevyengine:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
pablo-lua authored Sep 29, 2024
2 parents 813e548 + c32e0b9 commit 96961b8
Show file tree
Hide file tree
Showing 15 changed files with 397 additions and 37 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
21 changes: 9 additions & 12 deletions crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,10 @@ impl<'w> From<TicksMut<'w>> 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<Res<T>>` instead if the resource might not always exist.
/// Use [`Option<Res<T>>`] 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>,
Expand Down Expand Up @@ -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<ResMut<T>>` instead if the resource might not always exist.
/// Use [`Option<ResMut<T>>`] 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>,
Expand Down Expand Up @@ -684,11 +682,10 @@ impl<'w, T: Resource> From<ResMut<'w, T>> 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<NonSendMut<T>>` instead if the resource might not always exist.
/// Use [`Option<NonSendMut<T>>`] 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>,
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions crates/bevy_ecs/src/system/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
/// ```
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_ecs/src/system/commands/parallel_scope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ struct ParallelCommandQueue {
thread_queues: Parallel<CommandQueue>,
}

/// 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.
///
Expand Down
40 changes: 39 additions & 1 deletion crates/bevy_ecs/src/system/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`].
///
Expand Down Expand Up @@ -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<QuerySingle<D, F>>`] 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<F>,
}

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
}
}
4 changes: 4 additions & 0 deletions crates/bevy_ecs/src/system/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
156 changes: 149 additions & 7 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<T>` 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<D, F>;
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<QuerySingle<'a, D, F>>
{
type State = QueryState<D, F>;
type Item<'w, 's> = Option<QuerySingle<'w, D, F>>;

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<QuerySingle<'a, D, F>>
{
}

/// 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
Expand Down Expand Up @@ -1172,11 +1315,10 @@ unsafe impl<T: SystemBuffer> 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<NonSend<T>>` instead if the resource might not always exist.
/// Use [`Option<NonSend<T>>`] instead if the resource might not always exist.
pub struct NonSend<'w, T: 'static> {
pub(crate) value: &'w T,
ticks: ComponentTicks,
Expand Down
9 changes: 5 additions & 4 deletions crates/bevy_ecs/src/world/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -940,7 +940,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};
Expand Down Expand Up @@ -1037,9 +1038,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};
Expand Down
Loading

0 comments on commit 96961b8

Please sign in to comment.