Merge 52358069b9 into 877d278785
This commit is contained in:
commit
ec31f19b8d
@ -168,6 +168,17 @@ pub(crate) fn world_query_impl(
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply(state: &mut Self::State, system_meta: &#path::system::SystemMeta, world: &mut #path::world::World) {
|
||||
#(<#field_types as #path::query::WorldQuery>::apply(&mut state.#named_field_idents, system_meta, world);)*
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn queue(state: &mut Self::State, system_meta: &#path::system::SystemMeta, mut world: #path::world::DeferredWorld) {
|
||||
#(<#field_types as #path::query::WorldQuery>::queue(&mut state.#named_field_idents, system_meta, world.reborrow());)*
|
||||
}
|
||||
|
||||
fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool {
|
||||
true #(&& <#field_types>::matches_component_set(&state.#named_field_idents, _set_contains_id))*
|
||||
}
|
||||
|
||||
@ -6,9 +6,10 @@ use crate::{
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
|
||||
storage::{ComponentSparseSet, Table, TableRow},
|
||||
system::SystemMeta,
|
||||
world::{
|
||||
unsafe_world_cell::UnsafeWorldCell, EntityMut, EntityMutExcept, EntityRef, EntityRefExcept,
|
||||
FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
|
||||
unsafe_world_cell::UnsafeWorldCell, DeferredWorld, EntityMut, EntityMutExcept, EntityRef,
|
||||
EntityRefExcept, FilteredEntityMut, FilteredEntityRef, Mut, Ref, World,
|
||||
},
|
||||
};
|
||||
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
|
||||
@ -2278,6 +2279,16 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
|
||||
) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
const HAS_DEFERRED: bool = T::HAS_DEFERRED;
|
||||
|
||||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
<T as WorldQuery>::apply(state, system_meta, world);
|
||||
}
|
||||
|
||||
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
|
||||
<T as WorldQuery>::queue(state, system_meta, world);
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: defers to soundness of `T: WorldQuery` impl
|
||||
@ -2489,6 +2500,268 @@ impl<T: Component> ReleaseStateQueryData for Has<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// gate `DeferredMut` behind support for Parallel<T>
|
||||
bevy_utils::cfg::parallel! {
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use crate::entity::EntityHashMap;
|
||||
use bevy_utils::Parallel;
|
||||
|
||||
/// Provides "fake" mutable access to the component `T`
|
||||
///
|
||||
/// `DeferredMut` only accesses `&T` from the world, but when mutably
|
||||
/// dereferenced will clone it and return a reference to that cloned value.
|
||||
/// Once the `DeferredMut` is dropped, the query keeps track of the new value
|
||||
/// and inserts it into the world at the next sync point.
|
||||
///
|
||||
/// This can be used to "mutably" access immutable components!
|
||||
/// However, this will still be slower than direct mutation, so this should
|
||||
/// mainly be used for its ergonomics.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_ecs::query::DeferredMut;
|
||||
/// # use bevy_ecs::query::With;
|
||||
/// # use bevy_ecs::system::IntoSystem;
|
||||
/// # use bevy_ecs::system::Query;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Poisoned;
|
||||
/// #
|
||||
/// #[derive(Component, Clone)]
|
||||
/// #[component(immutable)]
|
||||
/// struct Health(u32);
|
||||
///
|
||||
/// fn tick_poison(mut health_query: Query<DeferredMut<Health>, With<Poisoned>>) {
|
||||
/// for mut health in &health_query {
|
||||
/// health.0 -= 1;
|
||||
/// }
|
||||
/// }
|
||||
/// # bevy_ecs::system::assert_is_system(tick_poison);
|
||||
/// ```
|
||||
///
|
||||
/// # Footguns
|
||||
///
|
||||
/// 1. The mutations tracked by `DeferredMut` will *not* be applied if used
|
||||
/// through the manual [`QueryState`](super::QueryState) API. Instead, it
|
||||
/// should be used through a query in a system param or [`SystemState`](crate::system::SystemState).
|
||||
///
|
||||
/// 2. It's possible to query multiple `DeferredMut` values from the same entity.
|
||||
/// However, since mutations are deferred, each new value won't see the changes
|
||||
/// applied to previous iterations.
|
||||
///
|
||||
/// Normally, the final iteration will be the one that "wins" and gets inserted
|
||||
/// onto the entity, but parallelism can mess with that, too. Since `DeferredMut`
|
||||
/// internally uses a thread-local [`EntityHashMap`] to keep track of mutations,
|
||||
/// if two `DeferredMut` values for the same entity are created in the same system
|
||||
/// on different threads, then they'll each be inserted into the entity in an
|
||||
/// undetermined order.
|
||||
pub struct DeferredMut<'w, 's, T: Component> {
|
||||
entity: Entity,
|
||||
old: &'w T,
|
||||
new: Option<T>,
|
||||
record: &'s DeferredMutations<T>,
|
||||
}
|
||||
|
||||
impl<'w, 's, T: Component> DeferredMut<'w, 's, T> {
|
||||
/// Returns a reference to the `T` value still present in the ECS
|
||||
#[inline]
|
||||
pub fn stale(&self) -> &'w T {
|
||||
self.old
|
||||
}
|
||||
|
||||
/// Returns a reference to the `T` value currently being updated.
|
||||
/// If none is present yet, this method will clone from `Self::stale`
|
||||
#[inline]
|
||||
pub fn fresh(&mut self) -> &mut T where T: Clone {
|
||||
self.get_fresh_or_insert(self.old.clone())
|
||||
}
|
||||
|
||||
/// Returns a (possibly absent) reference to the `T` value currently being updated.
|
||||
#[inline]
|
||||
pub fn get_fresh(&mut self) -> Option<&mut T> {
|
||||
self.new.as_mut()
|
||||
}
|
||||
|
||||
/// Returns a reference to the `T` value currently being updated.
|
||||
/// If absent, it will insert the provided value.
|
||||
#[inline]
|
||||
pub fn get_fresh_or_insert(&mut self, value: T) -> &mut T {
|
||||
self.new.get_or_insert(value)
|
||||
}
|
||||
|
||||
/// Replaces the `T` value currently being updated
|
||||
#[inline]
|
||||
pub fn insert(&mut self, value: T) {
|
||||
self.new = Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 's, T: Component> Drop for DeferredMut<'w, 's, T> {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
if let Some(new) = self.new.take() {
|
||||
self.record.insert(self.entity, new);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 's, T: Component> Deref for DeferredMut<'w, 's, T> {
|
||||
type Target = T;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.new.as_ref().unwrap_or(self.old)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'w, 's, T: Component + Clone> DerefMut for DeferredMut<'w, 's, T> {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.fresh()
|
||||
}
|
||||
}
|
||||
|
||||
/// The [`WorldQuery::State`] type for [`DeferredMut`]
|
||||
pub struct DeferredMutState<T: Component> {
|
||||
internal: <&'static T as WorldQuery>::State,
|
||||
record: DeferredMutations<T>,
|
||||
}
|
||||
|
||||
struct DeferredMutations<T: Component>(Parallel<EntityHashMap<T>>);
|
||||
|
||||
impl<T: Component> Default for DeferredMutations<T> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Component> DeferredMutations<T> {
|
||||
#[inline]
|
||||
fn insert(&self, entity: Entity, component: T) {
|
||||
self.0.scope(|map| map.insert(entity, component));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn drain(&mut self) -> impl Iterator<Item = (Entity, T)> {
|
||||
self.0.drain()
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: impl defers to `<&T as WorldQuery>` for all methods
|
||||
unsafe impl<'__w, '__s, T: Component> WorldQuery for DeferredMut<'__w, '__s, T> {
|
||||
type Fetch<'w> = ReadFetch<'w, T>;
|
||||
|
||||
type State = DeferredMutState<T>;
|
||||
|
||||
fn shrink_fetch<'wlong: 'wshort, 'wshort>(fetch: Self::Fetch<'wlong>) -> Self::Fetch<'wshort> {
|
||||
fetch
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w, 's>(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
state: &'s Self::State,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
// SAFETY: invariants are upheld by the caller
|
||||
unsafe { <&T as WorldQuery>::init_fetch(world, &state.internal, last_run, this_run) }
|
||||
}
|
||||
|
||||
const IS_DENSE: bool = <&T as WorldQuery>::IS_DENSE;
|
||||
|
||||
unsafe fn set_archetype<'w>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
archetype: &'w Archetype,
|
||||
table: &'w Table,
|
||||
) {
|
||||
<&T as WorldQuery>::set_archetype(fetch, &state.internal, archetype, table);
|
||||
}
|
||||
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
<&T as WorldQuery>::set_table(fetch, &state.internal, table);
|
||||
}
|
||||
|
||||
fn update_component_access(state: &Self::State, access: &mut FilteredAccess<ComponentId>) {
|
||||
<&T as WorldQuery>::update_component_access(&state.internal, access);
|
||||
}
|
||||
|
||||
fn init_state(world: &mut World) -> Self::State {
|
||||
DeferredMutState {
|
||||
internal: <&T as WorldQuery>::init_state(world),
|
||||
record: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_state(components: &Components) -> Option<Self::State> {
|
||||
Some(DeferredMutState {
|
||||
internal: <&T as WorldQuery>::get_state(components)?,
|
||||
record: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
fn matches_component_set(
|
||||
state: &Self::State,
|
||||
set_contains_id: &impl Fn(ComponentId) -> bool,
|
||||
) -> bool {
|
||||
<&T as WorldQuery>::matches_component_set(&state.internal, set_contains_id)
|
||||
}
|
||||
|
||||
const HAS_DEFERRED: bool = true;
|
||||
|
||||
fn apply(state: &mut Self::State, _system_meta: &SystemMeta, world: &mut World) {
|
||||
world.insert_batch(state.record.drain());
|
||||
}
|
||||
|
||||
fn queue(state: &mut Self::State, _system_meta: &SystemMeta, mut world: DeferredWorld) {
|
||||
world
|
||||
.commands()
|
||||
.insert_batch(state.record.drain().collect::<alloc::vec::Vec<_>>());
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: DeferredMut<T> defers to &T internally, so it must be readonly and Self::ReadOnly = Self.
|
||||
unsafe impl<'__w, '__s, T: Component> QueryData for DeferredMut<'__w, '__s, T> {
|
||||
const IS_READ_ONLY: bool = true;
|
||||
|
||||
type ReadOnly = Self;
|
||||
|
||||
type Item<'w, 's> = DeferredMut<'w, 's, T>;
|
||||
|
||||
fn shrink<'wlong: 'wshort, 'wshort, 's>(
|
||||
item: Self::Item<'wlong, 's>,
|
||||
) -> Self::Item<'wshort, 's> {
|
||||
item
|
||||
}
|
||||
|
||||
unsafe fn fetch<'w, 's>(
|
||||
state: &'s Self::State,
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
entity: Entity,
|
||||
table_row: TableRow,
|
||||
) -> Self::Item<'w, 's> {
|
||||
// SAFETY: invariants are upheld by the caller
|
||||
let old =
|
||||
unsafe { <&T as QueryData>::fetch(&state.internal, fetch, entity, table_row) };
|
||||
DeferredMut {
|
||||
entity,
|
||||
old,
|
||||
// NOTE: we could try to get an existing updated component from the record,
|
||||
// but we can't reliably do that across all threads. Better to say that all
|
||||
// newly-created DeferredMut values will match what's in the ECS.
|
||||
new: None,
|
||||
record: &state.record,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: Tracked<T> only accesses &T from the world. Though it provides mutable access, it only
|
||||
// applies those changes through commands.
|
||||
unsafe impl<'__w, '__s, T: Component> ReadOnlyQueryData for DeferredMut<'__w, '__s, T> {}
|
||||
}
|
||||
|
||||
/// The `AnyOf` query parameter fetches entities with any of the component types included in T.
|
||||
///
|
||||
/// `Query<AnyOf<(&A, &B, &mut C)>>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), Or<(With<A>, With<B>, With<C>)>>`.
|
||||
@ -2586,6 +2859,10 @@ macro_rules! impl_anytuple_fetch {
|
||||
unused_variables,
|
||||
reason = "Zero-length tuples won't use any of the parameters."
|
||||
)]
|
||||
#[allow(
|
||||
unused_mut,
|
||||
reason = "Zero-length tuples won't access any of the parameters mutably."
|
||||
)]
|
||||
#[allow(
|
||||
clippy::unused_unit,
|
||||
reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case."
|
||||
@ -2683,6 +2960,16 @@ macro_rules! impl_anytuple_fetch {
|
||||
let ($($name,)*) = _state;
|
||||
false $(|| $name::matches_component_set($name, _set_contains_id))*
|
||||
}
|
||||
|
||||
const HAS_DEFERRED: bool = false $(|| $name::HAS_DEFERRED)*;
|
||||
|
||||
fn apply(($($state,)*): &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
$(<$name as WorldQuery>::apply($state, system_meta, world);)*
|
||||
}
|
||||
|
||||
fn queue(($($state,)*): &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) {
|
||||
$(<$name as WorldQuery>::queue($state, system_meta, world.reborrow());)*
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
|
||||
@ -4,7 +4,8 @@ use crate::{
|
||||
entity::{Entities, Entity},
|
||||
query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery},
|
||||
storage::{ComponentSparseSet, Table, TableRow},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||
system::SystemMeta,
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
};
|
||||
use bevy_ptr::{ThinSlicePtr, UnsafeCellDeref};
|
||||
use bevy_utils::prelude::DebugName;
|
||||
@ -378,6 +379,10 @@ macro_rules! impl_or_query_filter {
|
||||
unused_variables,
|
||||
reason = "Zero-length tuples won't use any of the parameters."
|
||||
)]
|
||||
#[allow(
|
||||
unused_mut,
|
||||
reason = "Zero-length tuples won't access any of the parameters mutably."
|
||||
)]
|
||||
#[allow(
|
||||
clippy::unused_unit,
|
||||
reason = "Zero-length tuples will generate some function bodies equivalent to `()`; however, this macro is meant for all applicable tuples, and as such it makes no sense to rewrite it just for that case."
|
||||
@ -478,6 +483,16 @@ macro_rules! impl_or_query_filter {
|
||||
let ($($filter,)*) = state;
|
||||
false $(|| $filter::matches_component_set($filter, set_contains_id))*
|
||||
}
|
||||
|
||||
const HAS_DEFERRED: bool = false $(|| $filter::HAS_DEFERRED)*;
|
||||
|
||||
fn apply(($($state,)*): &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
$(<$filter as WorldQuery>::apply($state, system_meta, world);)*
|
||||
}
|
||||
|
||||
fn queue(($($state,)*): &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) {
|
||||
$(<$filter as WorldQuery>::queue($state, system_meta, world.reborrow());)*
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
|
||||
@ -3,7 +3,8 @@ use crate::{
|
||||
component::{ComponentId, Components, Tick},
|
||||
query::FilteredAccess,
|
||||
storage::Table,
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, World},
|
||||
system::SystemMeta,
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
};
|
||||
use variadics_please::all_tuples;
|
||||
|
||||
@ -121,6 +122,34 @@ pub unsafe trait WorldQuery {
|
||||
/// access to [`Components`].
|
||||
fn get_state(components: &Components) -> Option<Self::State>;
|
||||
|
||||
/// Returns true if (and only if) this [`WorldQuery`] contains deferred state
|
||||
/// that needs to be applied at the next sync point. If this is set to `false`,
|
||||
/// `apply` or `queue` may not be called.
|
||||
const HAS_DEFERRED: bool = false;
|
||||
|
||||
/// Applies any deferred mutations stored in this [`WorldQuery`]'s state.
|
||||
/// This is used to apply [`Commands`] during [`ApplyDeferred`](crate::prelude::ApplyDeferred).
|
||||
///
|
||||
/// If this is not a no-op, then `HAS_DEFERRED` should be `true`
|
||||
///
|
||||
/// [`Commands`]: crate::prelude::Commands
|
||||
#[inline]
|
||||
#[expect(
|
||||
unused_variables,
|
||||
reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion."
|
||||
)]
|
||||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {}
|
||||
|
||||
/// Queues any deferred mutations to be applied at the next [`ApplyDeferred`](crate::prelude::ApplyDeferred).
|
||||
///
|
||||
/// If this is not a no-op, then `HAS_DEFERRED` should be `true`
|
||||
#[inline]
|
||||
#[expect(
|
||||
unused_variables,
|
||||
reason = "The parameters here are intentionally unused by the default implementation; however, putting underscores here will result in the underscores being copied by rust-analyzer's tab completion."
|
||||
)]
|
||||
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {}
|
||||
|
||||
/// Returns `true` if this query matches a set of components. Otherwise, returns `false`.
|
||||
///
|
||||
/// Used to check which [`Archetype`]s can be skipped by the query
|
||||
@ -211,6 +240,22 @@ macro_rules! impl_tuple_world_query {
|
||||
Some(($($name::get_state(components)?,)*))
|
||||
}
|
||||
|
||||
const HAS_DEFERRED: bool = false $(|| $name::HAS_DEFERRED)*;
|
||||
|
||||
#[inline]
|
||||
fn apply(($($name,)*): &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
$($name::apply($name, system_meta, world);)*
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[allow(
|
||||
unused_mut,
|
||||
reason = "The `world` parameter is unused for zero-length tuples; however, it must be mutable for other lengths of tuples."
|
||||
)]
|
||||
fn queue(($($name,)*): &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) {
|
||||
$($name::queue($name, system_meta, world.reborrow());)*
|
||||
}
|
||||
|
||||
fn matches_component_set(state: &Self::State, set_contains_id: &impl Fn(ComponentId) -> bool) -> bool {
|
||||
let ($($name,)*) = state;
|
||||
true $(&& $name::matches_component_set($name, set_contains_id))*
|
||||
|
||||
@ -355,6 +355,10 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Qu
|
||||
world,
|
||||
);
|
||||
component_access_set.add(state.component_access.clone());
|
||||
|
||||
if D::HAS_DEFERRED || F::HAS_DEFERRED {
|
||||
system_meta.set_has_deferred();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -370,6 +374,18 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam for Qu
|
||||
// The caller ensures the world matches the one used in init_state.
|
||||
unsafe { state.query_unchecked_with_ticks(world, system_meta.last_run, change_tick) }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
|
||||
D::apply(&mut state.fetch_state, system_meta, world);
|
||||
F::apply(&mut state.filter_state, system_meta, world);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn queue(state: &mut Self::State, system_meta: &SystemMeta, mut world: DeferredWorld) {
|
||||
D::queue(&mut state.fetch_state, system_meta, world.reborrow());
|
||||
F::queue(&mut state.filter_state, system_meta, world);
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_component_access_compatibility(
|
||||
|
||||
45
release-content/release-notes/new_querydata.md
Normal file
45
release-content/release-notes/new_querydata.md
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
title: New QueryData Types
|
||||
authors: ["@ecoskey"]
|
||||
pull_requests: [19602]
|
||||
---
|
||||
|
||||
Bevy queries have some new powers for advanced users. Namely, custom `WorldQuery`
|
||||
implementations can store and apply "deferred" mutations, just like `Commands`!
|
||||
This release includes a few new types making use of this capability, and
|
||||
we're sure third-party crates will find all kinds of new ways to do cool stuff
|
||||
with this.
|
||||
|
||||
## `DeferredMut`
|
||||
|
||||
When working with immutable components in Bevy, the acts of reading and writing
|
||||
component values are very clearly separated. This can be valuable, especially
|
||||
if a component has expensive hooks or observers attached and `insert`ing it
|
||||
has a significant cost, but in some cases it can feel like boilerplate.
|
||||
|
||||
`DeferredMut` is meant to improve the ergonomics of the latter case, by providing
|
||||
"fake" mutable access to any component, even immutable ones! Internally, it
|
||||
keeps track of any modifications and inserts them into the ECS at the next
|
||||
sync point.
|
||||
|
||||
```rs
|
||||
// without `DeferredMut`
|
||||
pub fn tick_poison(
|
||||
mut commands: Commands,
|
||||
query: Query<(Entity, &Health), With<Poisoned>>
|
||||
) {
|
||||
for (entity, Health(health_points)) in query {
|
||||
commands.insert(entity, Health(health_points - 1))
|
||||
}
|
||||
}
|
||||
|
||||
// with `DeferredMut`
|
||||
pub fn tick_poison(
|
||||
mut health_query: Query<DeferredMut<Health>, With<Poisoned>>
|
||||
) {
|
||||
for mut health in &health_query {
|
||||
health.0 -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
Loading…
Reference in New Issue
Block a user