Skip to content

Commit 0cfb567

Browse files
authored
Try #2507:
2 parents ba2916c + 8f76330 commit 0cfb567

File tree

8 files changed

+232
-0
lines changed

8 files changed

+232
-0
lines changed

Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,10 @@ path = "examples/ecs/timers.rs"
317317
name = "query_bundle"
318318
path = "examples/ecs/query_bundle.rs"
319319

320+
[[example]]
321+
name = "dynamic_schedule"
322+
path = "examples/ecs/dynamic_schedule.rs"
323+
320324
# Games
321325
[[example]]
322326
name = "alien_cake_addict"
+137
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use super::{Schedule, StageLabel, SystemDescriptor};
2+
use crate::system::Command;
3+
use crate::world::World;
4+
5+
#[derive(Default)]
6+
pub struct SchedulerCommandQueue {
7+
items: Vec<Box<dyn SchedulerCommand>>,
8+
}
9+
10+
impl SchedulerCommandQueue {
11+
pub fn push<C>(&mut self, command: C)
12+
where
13+
C: SchedulerCommand,
14+
{
15+
self.items.push(Box::new(command));
16+
}
17+
18+
pub fn apply(&mut self, schedule: &mut Schedule, offset: usize) {
19+
for command in self.items.drain(offset..) {
20+
command.write(schedule);
21+
}
22+
}
23+
24+
pub fn len(&self) -> usize {
25+
self.items.len()
26+
}
27+
}
28+
29+
/// A [`Schedule`] mutation.
30+
pub trait SchedulerCommand: Send + Sync + 'static {
31+
fn write(self: Box<Self>, schedule: &mut Schedule);
32+
}
33+
34+
impl<T> Command for T
35+
where
36+
T: SchedulerCommand,
37+
{
38+
fn write(self, world: &mut World) {
39+
world.scheduler_commands.push(self);
40+
}
41+
}
42+
43+
pub struct InsertSystem<S>
44+
where
45+
S: StageLabel,
46+
{
47+
pub system: SystemDescriptor,
48+
pub stage_label: S,
49+
}
50+
51+
impl<S> SchedulerCommand for InsertSystem<S>
52+
where
53+
S: StageLabel,
54+
{
55+
fn write(self: Box<Self>, schedule: &mut Schedule) {
56+
schedule.add_system_to_stage(self.stage_label, self.system);
57+
}
58+
}
59+
60+
#[cfg(test)]
61+
mod tests {
62+
use crate::{
63+
prelude::ResMut,
64+
schedule::{
65+
InsertSystem, IntoSystemDescriptor, Schedule, SchedulerCommandQueue, SystemStage,
66+
},
67+
system::Commands,
68+
world::World,
69+
};
70+
71+
#[test]
72+
fn insert_system() {
73+
fn sample_system(mut _commands: Commands) {}
74+
let mut schedule = Schedule::default();
75+
schedule.add_stage("test", SystemStage::parallel());
76+
let mut queue = SchedulerCommandQueue::default();
77+
queue.push(InsertSystem {
78+
system: sample_system.into_descriptor(),
79+
stage_label: "test",
80+
});
81+
queue.apply(&mut schedule, 0);
82+
83+
let stage = schedule.get_stage::<SystemStage>(&"test").unwrap();
84+
assert_eq!(stage.parallel_systems().len(), 1);
85+
}
86+
87+
#[derive(Debug, Default, PartialEq)]
88+
struct TestResource(Option<()>);
89+
90+
#[test]
91+
fn insert_system_from_system() {
92+
fn sample_system(mut commands: Commands) {
93+
commands.insert_system(
94+
|mut res: ResMut<TestResource>| {
95+
res.0 = Some(());
96+
},
97+
"test",
98+
);
99+
}
100+
101+
let mut world = World::default();
102+
world.insert_resource(TestResource(None));
103+
104+
let mut schedule = Schedule::default();
105+
schedule.add_stage("test", SystemStage::parallel());
106+
schedule.add_system_to_stage("test", sample_system);
107+
schedule.run_once(&mut world);
108+
109+
let stage = schedule.get_stage::<SystemStage>(&"test").unwrap();
110+
assert_eq!(stage.parallel_systems().len(), 2);
111+
112+
schedule.run_once(&mut world);
113+
assert_eq!(
114+
world.get_resource::<TestResource>(),
115+
Some(&TestResource(Some(())))
116+
);
117+
}
118+
119+
#[test]
120+
fn insert_with_nested_schedule() {
121+
fn sample_system(mut commands: Commands) {
122+
commands.insert_system(|| {}, "test");
123+
}
124+
125+
let mut world = World::default();
126+
127+
let nested_schedule = Schedule::default();
128+
let mut schedule = Schedule::default();
129+
schedule.add_stage("test", SystemStage::parallel());
130+
schedule.add_system_to_stage("test", sample_system);
131+
schedule.add_stage("nested", nested_schedule);
132+
schedule.run_once(&mut world);
133+
134+
let stage = schedule.get_stage::<SystemStage>(&"test").unwrap();
135+
assert_eq!(stage.parallel_systems().len(), 2);
136+
}
137+
}

crates/bevy_ecs/src/schedule/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
mod commands;
12
mod executor;
23
mod executor_parallel;
34
pub mod graph_utils;
@@ -9,6 +10,7 @@ mod system_container;
910
mod system_descriptor;
1011
mod system_set;
1112

13+
pub use commands::*;
1214
pub use executor::*;
1315
pub use executor_parallel::*;
1416
pub use graph_utils::GraphNode;
@@ -198,6 +200,8 @@ impl Schedule {
198200
}
199201

200202
pub fn run_once(&mut self, world: &mut World) {
203+
let existing_commands = world.scheduler_commands.len();
204+
201205
for label in self.stage_order.iter() {
202206
#[cfg(feature = "trace")]
203207
let stage_span =
@@ -207,6 +211,8 @@ impl Schedule {
207211
let stage = self.stages.get_mut(label).unwrap();
208212
stage.run(world);
209213
}
214+
215+
world.scheduler_commands.apply(self, existing_commands);
210216
}
211217

212218
/// Iterates over all of schedule's stages and their labels, in execution order.

crates/bevy_ecs/src/schedule/system_descriptor.rs

+6
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ pub trait IntoSystemDescriptor<Params> {
4545

4646
pub struct SystemLabelMarker;
4747

48+
impl IntoSystemDescriptor<()> for SystemDescriptor {
49+
fn into_descriptor(self) -> SystemDescriptor {
50+
self
51+
}
52+
}
53+
4854
impl IntoSystemDescriptor<()> for ParallelSystemDescriptor {
4955
fn into_descriptor(self) -> SystemDescriptor {
5056
SystemDescriptor::Parallel(self)

crates/bevy_ecs/src/system/commands/mod.rs

+37
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::{
44
bundle::Bundle,
55
component::Component,
66
entity::{Entities, Entity},
7+
schedule::{InsertSystem, IntoSystemDescriptor, StageLabel},
78
world::World,
89
};
910
use bevy_utils::tracing::debug;
@@ -152,6 +153,42 @@ impl<'a> Commands<'a> {
152153
});
153154
}
154155

156+
/// Inserts a [`crate::system::System`] into a [`crate::schedule::Stage`]
157+
///
158+
/// # Example
159+
///
160+
/// ```
161+
/// use bevy_ecs::prelude::*;
162+
///
163+
/// fn another_system() {
164+
/// // A normal system like any other
165+
/// }
166+
///
167+
/// fn example_system(mut commands: Commands) {
168+
/// commands.insert_system(another_system, "some stage");
169+
/// }
170+
///
171+
/// let mut world = World::default();
172+
/// let mut schedule = Schedule::default();
173+
/// schedule.add_stage("some stage", SystemStage::parallel());
174+
/// schedule.add_system_to_stage("some stage", example_system);
175+
/// // When we run the schedule
176+
/// schedule.run_once(&mut world);
177+
/// // We should now have 2 systems in "test", the initial system and the inserted system
178+
/// let stage = schedule.get_stage::<SystemStage>(&"some stage").unwrap();
179+
/// assert_eq!(stage.parallel_systems().len(), 2);
180+
/// ```
181+
pub fn insert_system<T, S, Params>(&mut self, system: T, stage_label: S)
182+
where
183+
T: IntoSystemDescriptor<Params>,
184+
S: StageLabel,
185+
{
186+
self.queue.push(InsertSystem {
187+
system: system.into_descriptor(),
188+
stage_label,
189+
});
190+
}
191+
155192
/// Adds a command directly to the command list.
156193
pub fn add<C: Command>(&mut self, command: C) {
157194
self.queue.push(command);

crates/bevy_ecs/src/world/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use crate::{
1818
},
1919
entity::{Entities, Entity},
2020
query::{FilterFetch, QueryState, WorldQuery},
21+
schedule::SchedulerCommandQueue,
2122
storage::{Column, SparseSet, Storages},
2223
};
2324
use std::{
@@ -53,6 +54,7 @@ pub struct World {
5354
main_thread_validator: MainThreadValidator,
5455
pub(crate) change_tick: AtomicU32,
5556
pub(crate) last_change_tick: u32,
57+
pub(crate) scheduler_commands: SchedulerCommandQueue,
5658
}
5759

5860
impl Default for World {
@@ -71,6 +73,7 @@ impl Default for World {
7173
// are detected on first system runs and for direct world queries.
7274
change_tick: AtomicU32::new(1),
7375
last_change_tick: 0,
76+
scheduler_commands: Default::default(),
7477
}
7578
}
7679
}

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ Example | File | Description
170170
`system_param` | [`ecs/system_param.rs`](./ecs/system_param.rs) | Illustrates creating custom system parameters with `SystemParam`
171171
`system_sets` | [`ecs/system_sets.rs`](./ecs/system_sets.rs) | Shows `SystemSet` use along with run criterion
172172
`timers` | [`ecs/timers.rs`](./ecs/timers.rs) | Illustrates ticking `Timer` resources inside systems and handling their state
173+
`dynamic_schedule` | [`ecs/dynamic_schedule.rs`](./ecs/dynamic_schedule.rs) | Shows how to modify the schedule from systems
173174

174175
## Games
175176

examples/ecs/dynamic_schedule.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use bevy::{core::FixedTimestep, prelude::*};
2+
use rand::Rng;
3+
4+
fn main() {
5+
App::build()
6+
.add_plugins(DefaultPlugins)
7+
.add_stage_after(
8+
CoreStage::Update,
9+
"slow",
10+
SystemStage::parallel().with_run_criteria(FixedTimestep::step(1.0)),
11+
)
12+
.add_startup_system(setup)
13+
.add_system(dynamic.with_run_criteria(FixedTimestep::step(1.0)))
14+
.run();
15+
}
16+
17+
fn setup(mut commands: Commands) {
18+
commands.spawn_bundle(OrthographicCameraBundle::new_2d());
19+
}
20+
21+
fn dynamic(mut commands: Commands, mut system_counter: Local<u64>) {
22+
let count = system_counter.clone();
23+
*system_counter += 1;
24+
let closure = move |mut commands: Commands,
25+
asset_server: Res<AssetServer>,
26+
mut materials: ResMut<Assets<ColorMaterial>>| {
27+
info!("Hello from system {}", count);
28+
29+
let mut rng = rand::thread_rng();
30+
let texture_handle = asset_server.load("branding/icon.png");
31+
commands.spawn_bundle(SpriteBundle {
32+
material: materials.add(texture_handle.into()),
33+
transform: Transform::from_xyz(rng.gen_range(-400f32..400f32), rng.gen_range(-400f32..400f32), 0.0),
34+
..Default::default()
35+
});
36+
};
37+
commands.insert_system(closure, "slow");
38+
}

0 commit comments

Comments
 (0)