-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Create OnDeferred
to allow hooking into every ApplyDeferred
action.
#19818
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
---|---|---|
|
@@ -20,7 +20,7 @@ use crate::{ | |
SystemWithAccess, | ||
}, | ||
system::ScheduleSystem, | ||
world::{unsafe_world_cell::UnsafeWorldCell, World}, | ||
world::{unsafe_world_cell::UnsafeWorldCell, OnDeferred, World}, | ||
}; | ||
#[cfg(feature = "hotpatching")] | ||
use crate::{event::Events, HotPatched}; | ||
|
@@ -787,6 +787,8 @@ fn apply_deferred( | |
systems: &[SyncUnsafeCell<SystemWithAccess>], | ||
world: &mut World, | ||
) -> Result<(), Box<dyn Any + Send>> { | ||
OnDeferred::execute(world); | ||
|
||
for system_index in unapplied_systems.ones() { | ||
// SAFETY: none of these systems are running, no other references exist | ||
let system = &mut unsafe { &mut *systems[system_index].get() }.system; | ||
|
@@ -868,11 +870,15 @@ impl MainThreadExecutor { | |
|
||
#[cfg(test)] | ||
mod tests { | ||
use std::sync::Mutex; | ||
|
||
use alloc::{sync::Arc, vec}; | ||
|
||
use crate::{ | ||
prelude::Resource, | ||
schedule::{ExecutorKind, IntoScheduleConfigs, Schedule}, | ||
system::Commands, | ||
world::World, | ||
world::{OnDeferred, World}, | ||
}; | ||
|
||
#[derive(Resource)] | ||
|
@@ -908,4 +914,44 @@ mod tests { | |
schedule.add_systems(((|_: Commands| {}), |_: Commands| {}).chain()); | ||
schedule.run(&mut world); | ||
} | ||
|
||
#[test] | ||
fn executes_on_deferred() { | ||
let mut world = World::new(); | ||
let mut schedule = Schedule::default(); | ||
schedule.set_executor_kind(ExecutorKind::MultiThreaded); | ||
|
||
let result = Arc::new(Mutex::new(vec![])); | ||
|
||
let result_clone = result.clone(); | ||
let my_system = move |mut commands: Commands| { | ||
let result_clone = result_clone.clone(); | ||
commands.queue(move |_: &mut World| { | ||
result_clone.lock().unwrap().push("my_system"); | ||
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. I would prefer that this test does not potentially block so a bug would not cause deadlocking the test here. Generally even I know, they cannot run in parallel because of the exclusive world access of the closure you add, but just the other day we realized we mistakenly allow Same at the other lock below and the single threaded test. My suggestion is to put the vector here in a resource. |
||
}); | ||
}; | ||
schedule.add_systems((my_system.clone(), my_system.clone(), my_system).chain()); | ||
|
||
world.insert_resource({ | ||
let mut on_deferred = OnDeferred::default(); | ||
let result_clone = result.clone(); | ||
on_deferred.add(move |_: &mut World| { | ||
result_clone.lock().unwrap().push("on_deferred"); | ||
}); | ||
on_deferred | ||
}); | ||
|
||
schedule.run(&mut world); | ||
assert_eq!( | ||
&*result.lock().unwrap(), | ||
&[ | ||
"on_deferred", | ||
"my_system", | ||
"on_deferred", | ||
"my_system", | ||
"on_deferred", | ||
"my_system", | ||
] | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,7 +12,7 @@ use crate::{ | |
schedule::{ | ||
is_apply_deferred, ConditionWithAccess, ExecutorKind, SystemExecutor, SystemSchedule, | ||
}, | ||
world::World, | ||
world::{OnDeferred, World}, | ||
}; | ||
#[cfg(feature = "hotpatching")] | ||
use crate::{event::Events, HotPatched}; | ||
|
@@ -205,6 +205,8 @@ impl SingleThreadedExecutor { | |
} | ||
|
||
fn apply_deferred(&mut self, schedule: &mut SystemSchedule, world: &mut World) { | ||
OnDeferred::execute(world); | ||
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. I think you need to update On the other hand, |
||
|
||
for system_index in self.unapplied_systems.ones() { | ||
let system = &mut schedule.systems[system_index].system; | ||
system.apply_deferred(world); | ||
|
@@ -255,3 +257,58 @@ fn evaluate_and_fold_conditions( | |
}) | ||
.fold(true, |acc, res| acc && res) | ||
} | ||
|
||
#[cfg(test)] | ||
#[cfg(feature = "std")] | ||
mod tests { | ||
use std::sync::Mutex; | ||
|
||
use alloc::{sync::Arc, vec}; | ||
|
||
use crate::{ | ||
prelude::{IntoScheduleConfigs, Schedule}, | ||
schedule::ExecutorKind, | ||
system::Commands, | ||
world::{OnDeferred, World}, | ||
}; | ||
|
||
#[test] | ||
fn executes_on_deferred() { | ||
let mut world = World::new(); | ||
let mut schedule = Schedule::default(); | ||
schedule.set_executor_kind(ExecutorKind::MultiThreaded); | ||
|
||
let result = Arc::new(Mutex::new(vec![])); | ||
|
||
let result_clone = result.clone(); | ||
let my_system = move |mut commands: Commands| { | ||
let result_clone = result_clone.clone(); | ||
commands.queue(move |_: &mut World| { | ||
result_clone.lock().unwrap().push("my_system"); | ||
}); | ||
}; | ||
schedule.add_systems((my_system.clone(), my_system.clone(), my_system).chain()); | ||
|
||
world.insert_resource({ | ||
let mut on_deferred = OnDeferred::default(); | ||
let result_clone = result.clone(); | ||
on_deferred.add(move |_: &mut World| { | ||
result_clone.lock().unwrap().push("on_deferred"); | ||
}); | ||
on_deferred | ||
}); | ||
|
||
schedule.run(&mut world); | ||
assert_eq!( | ||
&*result.lock().unwrap(), | ||
&[ | ||
"on_deferred", | ||
"my_system", | ||
"on_deferred", | ||
"my_system", | ||
"on_deferred", | ||
"my_system", | ||
] | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
use alloc::{boxed::Box, vec::Vec}; | ||
|
||
use crate::{ | ||
resource::Resource, | ||
world::{Mut, World}, | ||
}; | ||
|
||
/// A resource holding the set of all actions to take just before applying deferred actions. | ||
#[derive(Resource, Default)] | ||
pub struct OnDeferred(Vec<Box<dyn FnMut(&mut World) + Send + Sync + 'static>>); | ||
|
||
impl OnDeferred { | ||
/// Adds an action to be executed before applying deferred actions. | ||
pub fn add(&mut self, action: impl FnMut(&mut World) + Send + Sync + 'static) { | ||
self.0.push(Box::new(action)); | ||
} | ||
|
||
/// Executes the actions in [`OnDeferred`] from `world`. Does nothing if [`OnDeferred`] does not | ||
/// exist in the world. | ||
pub(crate) fn execute(world: &mut World) { | ||
world.try_resource_scope(|world, mut this: Mut<Self>| { | ||
for action in &mut this.0 { | ||
action(world); | ||
} | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,22 @@ | ||||||||
--- | ||||||||
title: Hook into every `ApplyDeferred` using `OnDeferred` | ||||||||
authors: ["@andriyDev"] | ||||||||
pull_requests: [] | ||||||||
--- | ||||||||
|
||||||||
Bevy now allows you to execute some code whenever `ApplyDeferred` is executed. This can be thought | ||||||||
of as a command that executes at every sync point. | ||||||||
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.
Suggested change
In contrast to "proper" commands that can run multiple times at a single sync point if they get queued again by other commands/hooks/observers. |
||||||||
|
||||||||
To use this, first init the `OnDeferred` resource (to ensure it exists), then add to it: | ||||||||
|
||||||||
```rust | ||||||||
app.init_resource::<OnDeferred>(); | ||||||||
app.world_mut().resource_mut::<OnDeferred>().add(|world: &mut World| { | ||||||||
Comment on lines
+13
to
+14
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. The
Suggested change
|
||||||||
// Do command stuff. | ||||||||
}); | ||||||||
``` | ||||||||
|
||||||||
For one potential example, you could send commands through a channel to your `OnDeferred` command. | ||||||||
Since it has access to `&mut World` it can then apply any commands in the channel. While this is now | ||||||||
supported, more standard approaches are preferable (e.g., creating a system that polls the channel). | ||||||||
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 won't the more standard approaches work for assets? I would have expected assets to be used at known points in each frame (maybe during extract?) and that scheduling a system to read a queue right before that would be sufficient. |
||||||||
This is provided for situations where users truly need to react at every sync point. |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't you want to run this at the end of the sync point so the closures see the effects of commands/hooks/observers? Or do you want it to be the other way around, that these see the effect of
OnDeferred
?Might be worth renaming this into either
PreDeferred
orPostDeferred
.