-
-
Notifications
You must be signed in to change notification settings - Fork 4k
[Merged by Bors] - Add a SystemParam
primitive for deferred mutations; allow #[derive]
ing more types of SystemParam
#6817
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 18 commits
c0a66aa
e819787
3bff701
7f324d6
bf9840e
1daccbf
cdf6bbd
b4b1461
3a64d16
78ea2cc
b0d4e2e
d585826
e568345
ff97bf1
bcba009
4a46300
6020791
1a24fa7
4a0d59b
a1b8f45
9637850
35c79fe
2817097
0ff8aee
6a1aa3d
a86ce13
ef12641
e403150
5ded352
094141d
27b6795
e85e34e
9d6212a
be1b372
542e42a
1a746a6
8ac61af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ use crate::{ | |
query::{ | ||
Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, | ||
}, | ||
system::{CommandQueue, Commands, Query, SystemMeta}, | ||
system::{Query, SystemMeta}, | ||
world::{FromWorld, World}, | ||
}; | ||
pub use bevy_ecs_macros::Resource; | ||
|
@@ -151,6 +151,8 @@ pub unsafe trait SystemParam: Sized { | |
|
||
/// Applies any deferred mutations stored in this [`SystemParam`]'s state. | ||
/// This is used to apply [`Commands`] at the end of a stage. | ||
/// | ||
/// [`Commands`]: crate::prelude::Commands | ||
#[inline] | ||
#[allow(unused_variables)] | ||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {} | ||
|
@@ -674,37 +676,6 @@ unsafe impl<'a, T: Resource> SystemParam for Option<ResMut<'a, T>> { | |
} | ||
} | ||
|
||
// SAFETY: Commands only accesses internal state | ||
unsafe impl<'w, 's> ReadOnlySystemParam for Commands<'w, 's> {} | ||
|
||
// SAFETY: only local state is accessed | ||
unsafe impl SystemParam for Commands<'_, '_> { | ||
type State = CommandQueue; | ||
type Item<'w, 's> = Commands<'w, 's>; | ||
|
||
fn init_state(_world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { | ||
Default::default() | ||
} | ||
|
||
fn apply(state: &mut Self::State, _system_meta: &SystemMeta, world: &mut World) { | ||
#[cfg(feature = "trace")] | ||
let _system_span = | ||
bevy_utils::tracing::info_span!("system_commands", name = _system_meta.name()) | ||
.entered(); | ||
state.apply(world); | ||
} | ||
|
||
#[inline] | ||
unsafe fn get_param<'w, 's>( | ||
state: &'s mut Self::State, | ||
_system_meta: &SystemMeta, | ||
world: &'w World, | ||
_change_tick: u32, | ||
) -> Self::Item<'w, 's> { | ||
Commands::new(state, world) | ||
} | ||
} | ||
|
||
/// SAFETY: only reads world | ||
unsafe impl<'w> ReadOnlySystemParam for &'w World {} | ||
|
||
|
@@ -868,6 +839,142 @@ unsafe impl<'a, T: FromWorld + Send + 'static> SystemParam for Local<'a, T> { | |
} | ||
} | ||
|
||
/// Types that can be used with [`Deferred<T>`] in systems. | ||
/// This allows storing system-local data which is used to defer [`World`] mutations. | ||
/// | ||
/// When implementing `SystemBuffer`, you should take care to perform as many | ||
/// computations up-front as possible. Buffers cannot be applied in parallel, | ||
/// so you should try to minimize the time spent in [`SystemBuffer::apply`]. | ||
pub trait SystemBuffer: FromWorld + Send + 'static { | ||
/// Applies any deferred mutations to the [`World`]. | ||
fn apply(&mut self, system_meta: &SystemMeta, world: &mut World); | ||
} | ||
|
||
/// A [`SystemParam`] that stores a buffer which gets applied to the [`World`] at the end of a stage. | ||
/// By using this to defer mutations, you can avoid mutable `World` data access within a system | ||
/// and allow it to run in parallel with more systems. | ||
/// | ||
/// The most common example of this pattern is [`Commands`], which uses [`Deferred<T>`] to defer mutations. | ||
/// | ||
/// [`Commands`]: crate::system::Commands | ||
/// | ||
/// # Examples | ||
/// | ||
/// ``` | ||
/// # use bevy_ecs::prelude::*; | ||
/// // Tracks whether or not there is a threat the player should be aware of. | ||
/// #[derive(Resource, Default)] | ||
/// pub struct Alarm(bool); | ||
/// | ||
/// // A threat from inside the settlement. | ||
/// #[derive(Component)] | ||
/// pub struct Criminal; | ||
/// | ||
/// // A threat from outside the settlement. | ||
/// #[derive(Component)] | ||
/// pub struct Monster; | ||
/// | ||
/// use bevy_ecs::system::{Deferred, SystemBuffer, SystemMeta}; | ||
/// | ||
/// // Uses deferred mutations to allow signalling the alarm from multiple systems in parallel. | ||
/// #[derive(Resource, Default)] | ||
/// struct AlarmFlag(bool); | ||
/// | ||
/// impl SystemBuffer for AlarmFlag { | ||
/// fn apply(&mut self, system_meta: &SystemMeta, world: &mut World) { | ||
/// if self.0 { | ||
/// world.resource_mut::<Alarm>().0 = true; | ||
/// self.0 = false; | ||
/// } | ||
/// } | ||
/// } | ||
/// | ||
/// // Sound the alarm if there is a criminal. | ||
/// fn alert_criminal( | ||
/// criminals: Query<&Criminal>, | ||
/// mut alarm: Deferred<AlarmFlag> | ||
/// ) { | ||
/// if criminals.iter().next().is_some() { | ||
/// alarm.0 = true; | ||
/// } | ||
/// } | ||
/// | ||
/// // Sound the alarm if there is a monster. | ||
/// fn alert_monster( | ||
/// monsters: Query<&Monster>, | ||
/// mut alarm: Deferred<AlarmFlag> | ||
/// ) { | ||
/// if monsters.iter().next().is_some() { | ||
/// alarm.0 = true; | ||
/// } | ||
/// } | ||
/// | ||
/// let mut world = World::new(); | ||
/// world.init_resource::<Alarm>(); | ||
/// | ||
/// let mut stage = SystemStage::parallel(); | ||
/// stage | ||
/// // These two systems have no conflicts and will run in parallel. | ||
/// .add_system(alert_criminal) | ||
/// .add_system(alert_monster); | ||
/// | ||
/// // There are no criminals or monsters, so the alarm is not sounded. | ||
/// stage.run(&mut world); | ||
/// assert_eq!(world.resource::<Alarm>().0, false); | ||
/// | ||
/// // Spawn a monster, which will cause the alarm to be sounded. | ||
/// world.spawn(Monster); | ||
/// stage.run(&mut world); | ||
/// assert_eq!(world.resource::<Alarm>().0, true); | ||
/// | ||
/// // Reset the alarm. | ||
/// world.resource_mut::<Alarm>().0 = false; | ||
/// | ||
/// // Spawn a criminal, which will cause the alarm to be sounded. | ||
/// world.spawn(Criminal); | ||
/// stage.run(&mut world); | ||
/// assert_eq!(world.resource::<Alarm>().0, true); | ||
joseph-gio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// ``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example does a great job of illustrating the API, but I am worried it might teach "bad practice". It might lead people to think that using such a deferred pattern is expected to be faster / perform better than simply mutating the resource normally from the system. That is not true: deferring actions via system buffers adds a lot of overhead (that typically outweighs gains from parallelism for such small systems), and in this example the operation (mutating a resource value) could have been done from parallel systems just fine. Deferred does not win anything. I think the example could be made better if it actually uses an operation that cannot be done from parallel systems without using Deferred, such as adding/removing components or spawning entities. "Sounding the alarm" could be done by spawning an entity or adding a component, instead of mutating a value. That way, we actually have a reason to use Deferred. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Why do you say that this adds a lot of overhead? That said, I agree with your other points. I believe I have addressed them all. |
||
pub struct Deferred<'a, T: SystemBuffer>(pub(crate) &'a mut T); | ||
|
||
impl<'a, T: SystemBuffer> Deref for Deferred<'a, T> { | ||
type Target = T; | ||
#[inline] | ||
fn deref(&self) -> &Self::Target { | ||
self.0 | ||
} | ||
} | ||
|
||
impl<'a, T: SystemBuffer> DerefMut for Deferred<'a, T> { | ||
#[inline] | ||
fn deref_mut(&mut self) -> &mut Self::Target { | ||
self.0 | ||
} | ||
} | ||
|
||
// SAFETY: Only local state is accessed. | ||
unsafe impl<T: SystemBuffer> ReadOnlySystemParam for Deferred<'_, T> {} | ||
|
||
// SAFETY: Only local state is accessed. | ||
unsafe impl<T: SystemBuffer> SystemParam for Deferred<'_, T> { | ||
type State = SyncCell<T>; | ||
type Item<'w, 's> = Deferred<'s, T>; | ||
fn init_state(world: &mut World, _system_meta: &mut SystemMeta) -> Self::State { | ||
SyncCell::new(T::from_world(world)) | ||
} | ||
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { | ||
state.get().apply(system_meta, world); | ||
} | ||
joseph-gio marked this conversation as resolved.
Show resolved
Hide resolved
|
||
unsafe fn get_param<'w, 's>( | ||
state: &'s mut Self::State, | ||
_system_meta: &SystemMeta, | ||
_world: &'w World, | ||
_change_tick: u32, | ||
) -> Self::Item<'w, 's> { | ||
Deferred(state.get()) | ||
} | ||
} | ||
|
||
/// A [`SystemParam`] that grants access to the entities that had their `T` [`Component`] removed. | ||
/// | ||
/// Note that this does not allow you to see which data existed before removal. | ||
|
Uh oh!
There was an error while loading. Please reload this page.