Skip to content

Commit 54247bc

Browse files
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 ```
1 parent 79e7f8a commit 54247bc

File tree

3 files changed

+63
-17
lines changed

3 files changed

+63
-17
lines changed

crates/bevy_ecs/src/system/exclusive_function_system.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,11 @@ where
117117
panic!("Cannot run exclusive systems with a shared World reference");
118118
}
119119

120-
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
120+
fn run_without_applying_deferred(
121+
&mut self,
122+
input: SystemIn<'_, Self>,
123+
world: &mut World,
124+
) -> Self::Out {
121125
world.last_change_tick_scope(self.system_meta.last_run, |world| {
122126
#[cfg(feature = "trace")]
123127
let _span_guard = self.system_meta.system_span.enter();

crates/bevy_ecs/src/system/system.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,25 @@ pub trait System: Send + Sync + 'static {
8383
///
8484
/// [`run_readonly`]: ReadOnlySystem::run_readonly
8585
fn run(&mut self, input: SystemIn<'_, Self>, world: &mut World) -> Self::Out {
86+
let ret = self.run_without_applying_deferred(input, world);
87+
self.apply_deferred(world);
88+
ret
89+
}
90+
91+
/// Runs the system with the given input in the world.
92+
///
93+
/// [`run_readonly`]: ReadOnlySystem::run_readonly
94+
fn run_without_applying_deferred(
95+
&mut self,
96+
input: SystemIn<'_, Self>,
97+
world: &mut World,
98+
) -> Self::Out {
8699
let world_cell = world.as_unsafe_world_cell();
87100
self.update_archetype_component_access(world_cell);
88101
// SAFETY:
89102
// - We have exclusive access to the entire world.
90103
// - `update_archetype_component_access` has been called.
91-
let ret = unsafe { self.run_unsafe(input, world_cell) };
92-
self.apply_deferred(world);
93-
ret
104+
unsafe { self.run_unsafe(input, world_cell) }
94105
}
95106

96107
/// Applies any [`Deferred`](crate::system::Deferred) system parameters (or other system buffers) of this system to the world.

crates/bevy_ecs/src/system/system_registry.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -213,11 +213,9 @@ impl World {
213213
/// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once),
214214
/// because it keeps local state between calls and change detection works correctly.
215215
///
216-
/// In order to run a chained system with an input, use [`World::run_system_with`] instead.
217-
///
218-
/// # Limitations
216+
/// Also runs any queued-up commands.
219217
///
220-
/// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
218+
/// In order to run a chained system with an input, use [`World::run_system_with`] instead.
221219
///
222220
/// # Examples
223221
///
@@ -305,9 +303,7 @@ impl World {
305303
/// Before running a system, it must first be registered.
306304
/// The method [`World::register_system`] stores a given system and returns a [`SystemId`].
307305
///
308-
/// # Limitations
309-
///
310-
/// - Stored systems cannot be recursive, they cannot call themselves through [`Commands::run_system`](crate::system::Commands).
306+
/// Also runs any queued-up commands.
311307
///
312308
/// # Examples
313309
///
@@ -336,38 +332,45 @@ impl World {
336332
I: SystemInput + 'static,
337333
O: 'static,
338334
{
339-
// lookup
335+
// Lookup
340336
let mut entity = self
341337
.get_entity_mut(id.entity)
342338
.map_err(|_| RegisteredSystemError::SystemIdNotRegistered(id))?;
343339

344-
// take ownership of system trait object
340+
// Take ownership of system trait object
345341
let RegisteredSystem {
346342
mut initialized,
347343
mut system,
348344
} = entity
349345
.take::<RegisteredSystem<I, O>>()
350346
.ok_or(RegisteredSystemError::Recursive(id))?;
351347

352-
// run the system
348+
// Run the system
353349
if !initialized {
354350
system.initialize(self);
355351
initialized = true;
356352
}
357353

358354
let result = if system.validate_param(self) {
359-
Ok(system.run(input, self))
355+
// Wait to run the commands until the system is available again.
356+
// This is needed so the systems can recursively run themselves.
357+
let ret = system.run_without_applying_deferred(input, self);
358+
system.queue_deferred(self.into());
359+
Ok(ret)
360360
} else {
361361
Err(RegisteredSystemError::InvalidParams(id))
362362
};
363363

364-
// return ownership of system trait object (if entity still exists)
364+
// Return ownership of system trait object (if entity still exists)
365365
if let Ok(mut entity) = self.get_entity_mut(id.entity) {
366366
entity.insert::<RegisteredSystem<I, O>>(RegisteredSystem {
367367
initialized,
368368
system,
369369
});
370370
}
371+
372+
// Run any commands enqueued by the system
373+
self.flush();
371374
result
372375
}
373376

@@ -509,8 +512,13 @@ impl<I: SystemInput, O> core::fmt::Debug for RegisteredSystemError<I, O> {
509512
}
510513
}
511514

515+
#[cfg(test)]
512516
mod tests {
513-
use crate::prelude::*;
517+
use core::cell::Cell;
518+
519+
use bevy_utils::default;
520+
521+
use crate::{prelude::*, system::SystemId};
514522

515523
#[derive(Resource, Default, PartialEq, Debug)]
516524
struct Counter(u8);
@@ -863,4 +871,27 @@ mod tests {
863871
Err(RegisteredSystemError::InvalidParams(_))
864872
));
865873
}
874+
875+
#[test]
876+
fn run_system_recursive() {
877+
std::thread_local! {
878+
static INVOCATIONS_LEFT: Cell<i32> = const { Cell::new(3) };
879+
static SYSTEM_ID: Cell<Option<SystemId>> = default();
880+
}
881+
882+
fn system(mut commands: Commands) {
883+
let count = INVOCATIONS_LEFT.get() - 1;
884+
INVOCATIONS_LEFT.set(count);
885+
if count > 0 {
886+
commands.run_system(SYSTEM_ID.get().unwrap());
887+
}
888+
}
889+
890+
let mut world = World::new();
891+
let id = world.register_system(system);
892+
SYSTEM_ID.set(Some(id));
893+
world.run_system(id).unwrap();
894+
895+
assert_eq!(INVOCATIONS_LEFT.get(), 0);
896+
}
866897
}

0 commit comments

Comments
 (0)