Recursive run_system (#18076)
# Objective Fixes #18030 ## Solution When running a one-shot system, requeue the system's command queue onto the world's command queue, then execute the later. If running the entire command queue of the world is undesired, I could add a new method to `RawCommandQueue` to only apply part of it. ## Testing See the new test. --- ## Showcase ```rust #[derive(Resource)] pub struct Test { id: SystemId, counter: u32, } let mut world = World::new(); let id = world.register_system(|mut commands: Commands, mut test: ResMut<Test>| { print!("{:?} ", test.counter); test.counter -= 1; if test.counter > 0 { commands.run_system(test.id); } }); world.insert_resource(Test { id, counter: 5 }); world.run_system(id).unwrap(); ``` ``` 5 4 3 2 1 ```
This commit is contained in:
parent
79e7f8ae0c
commit
54247bcf86
@ -117,7 +117,11 @@ where
|
|||||||
panic!("Cannot run exclusive systems with a shared World reference");
|
panic!("Cannot run exclusive systems with a shared World reference");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
|
fn run_without_applying_deferred(
|
||||||
|
&mut self,
|
||||||
|
input: SystemIn<'_, Self>,
|
||||||
|
world: &mut World,
|
||||||
|
) -> Self::Out {
|
||||||
world.last_change_tick_scope(self.system_meta.last_run, |world| {
|
world.last_change_tick_scope(self.system_meta.last_run, |world| {
|
||||||
#[cfg(feature = "trace")]
|
#[cfg(feature = "trace")]
|
||||||
let _span_guard = self.system_meta.system_span.enter();
|
let _span_guard = self.system_meta.system_span.enter();
|
||||||
|
@ -83,14 +83,25 @@ pub trait System: Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// [`run_readonly`]: ReadOnlySystem::run_readonly
|
/// [`run_readonly`]: ReadOnlySystem::run_readonly
|
||||||
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
|
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
|
||||||
|
let ret = self.run_without_applying_deferred(input, world);
|
||||||
|
self.apply_deferred(world);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the system with the given input in the world.
|
||||||
|
///
|
||||||
|
/// [`run_readonly`]: ReadOnlySystem::run_readonly
|
||||||
|
fn run_without_applying_deferred(
|
||||||
|
&mut self,
|
||||||
|
input: SystemIn<'_, Self>,
|
||||||
|
world: &mut World,
|
||||||
|
) -> Self::Out {
|
||||||
let world_cell = world.as_unsafe_world_cell();
|
let world_cell = world.as_unsafe_world_cell();
|
||||||
self.update_archetype_component_access(world_cell);
|
self.update_archetype_component_access(world_cell);
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - We have exclusive access to the entire world.
|
// - We have exclusive access to the entire world.
|
||||||
// - `update_archetype_component_access` has been called.
|
// - `update_archetype_component_access` has been called.
|
||||||
let ret = unsafe { self.run_unsafe(input, world_cell) };
|
unsafe { self.run_unsafe(input, world_cell) }
|
||||||
self.apply_deferred(world);
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Applies any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers) of this system to the world.
|
/// Applies any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers) of this system to the world.
|
||||||
|
@ -213,12 +213,10 @@ impl World {
|
|||||||
/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),
|
/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),
|
||||||
/// because it keeps local state between calls and change detection works correctly.
|
/// because it keeps local state between calls and change detection works correctly.
|
||||||
///
|
///
|
||||||
|
/// Also runs any queued-up commands.
|
||||||
|
///
|
||||||
/// In order to run a chained system with an input, use [`World::run_system_with`] instead.
|
/// In order to run a chained system with an input, use [`World::run_system_with`] instead.
|
||||||
///
|
///
|
||||||
/// # Limitations
|
|
||||||
///
|
|
||||||
/// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
|
|
||||||
///
|
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ## Running a system
|
/// ## Running a system
|
||||||
@ -305,9 +303,7 @@ impl World {
|
|||||||
/// Before running a system, it must first be registered.
|
/// Before running a system, it must first be registered.
|
||||||
/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
|
/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
|
||||||
///
|
///
|
||||||
/// # Limitations
|
/// Also runs any queued-up commands.
|
||||||
///
|
|
||||||
/// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
|
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
@ -336,12 +332,12 @@ impl World {
|
|||||||
I: SystemInput + 'static,
|
I: SystemInput + 'static,
|
||||||
O: 'static,
|
O: 'static,
|
||||||
{
|
{
|
||||||
// lookup
|
// Lookup
|
||||||
let mut entity = self
|
let mut entity = self
|
||||||
.get_entity_mut(id.entity)
|
.get_entity_mut(id.entity)
|
||||||
.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;
|
.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;
|
||||||
|
|
||||||
// take ownership of system trait object
|
// Take ownership of system trait object
|
||||||
let RegisteredSystem {
|
let RegisteredSystem {
|
||||||
mut initialized,
|
mut initialized,
|
||||||
mut system,
|
mut system,
|
||||||
@ -349,25 +345,32 @@ impl World {
|
|||||||
.take::<RegisteredSystem<I, O>>()
|
.take::<RegisteredSystem<I, O>>()
|
||||||
.ok_or(RegisteredSystemError::Recursive(id))?;
|
.ok_or(RegisteredSystemError::Recursive(id))?;
|
||||||
|
|
||||||
// run the system
|
// Run the system
|
||||||
if !initialized {
|
if !initialized {
|
||||||
system.initialize(self);
|
system.initialize(self);
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = if system.validate_param(self) {
|
let result = if system.validate_param(self) {
|
||||||
Ok(system.run(input, self))
|
// Wait to run the commands until the system is available again.
|
||||||
|
// This is needed so the systems can recursively run themselves.
|
||||||
|
let ret = system.run_without_applying_deferred(input, self);
|
||||||
|
system.queue_deferred(self.into());
|
||||||
|
Ok(ret)
|
||||||
} else {
|
} else {
|
||||||
Err(RegisteredSystemError::InvalidParams(id))
|
Err(RegisteredSystemError::InvalidParams(id))
|
||||||
};
|
};
|
||||||
|
|
||||||
// return ownership of system trait object (if entity still exists)
|
// Return ownership of system trait object (if entity still exists)
|
||||||
if let Ok(mut entity) = self.get_entity_mut(id.entity) {
|
if let Ok(mut entity) = self.get_entity_mut(id.entity) {
|
||||||
entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
|
entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
|
||||||
initialized,
|
initialized,
|
||||||
system,
|
system,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any commands enqueued by the system
|
||||||
|
self.flush();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -509,8 +512,13 @@ impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::prelude::*;
|
use core::cell::Cell;
|
||||||
|
|
||||||
|
use bevy_utils::default;
|
||||||
|
|
||||||
|
use crate::{prelude::*, system::SystemId};
|
||||||
|
|
||||||
#[derive(Resource, Default, PartialEq, Debug)]
|
#[derive(Resource, Default, PartialEq, Debug)]
|
||||||
struct Counter(u8);
|
struct Counter(u8);
|
||||||
@ -863,4 +871,27 @@ mod tests {
|
|||||||
Err(RegisteredSystemError::InvalidParams(_))
|
Err(RegisteredSystemError::InvalidParams(_))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_system_recursive() {
|
||||||
|
std::thread_local! {
|
||||||
|
static INVOCATIONS_LEFT: Cell<i32> = const { Cell::new(3) };
|
||||||
|
static SYSTEM_ID: Cell<Option<SystemId>> = default();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn system(mut commands: Commands) {
|
||||||
|
let count = INVOCATIONS_LEFT.get() - 1;
|
||||||
|
INVOCATIONS_LEFT.set(count);
|
||||||
|
if count > 0 {
|
||||||
|
commands.run_system(SYSTEM_ID.get().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
let id = world.register_system(system);
|
||||||
|
SYSTEM_ID.set(Some(id));
|
||||||
|
world.run_system(id).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(INVOCATIONS_LEFT.get(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user