Skip to content

Commit a3c0c05

Browse files
authored
Try #2507:
2 parents ba2916c + 98cb617 commit a3c0c05

File tree

8 files changed

+240
-0
lines changed

8 files changed

+240
-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"
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
pub fn is_empty(&self) -> bool {
29+
self.items.is_empty()
30+
}
31+
}
32+
33+
/// A [`Schedule`] mutation.
34+
pub trait SchedulerCommand: Send + Sync + 'static {
35+
fn write(self: Box<Self>, schedule: &mut Schedule);
36+
}
37+
38+
impl<T> Command for T
39+
where
40+
T: SchedulerCommand,
41+
{
42+
fn write(self, world: &mut World) {
43+
world.scheduler_commands.push(self);
44+
}
45+
}
46+
47+
pub struct InsertSystem<S>
48+
where
49+
S: StageLabel,
50+
{
51+
pub system: SystemDescriptor,
52+
pub stage_label: S,
53+
}
54+
55+
impl<S> SchedulerCommand for InsertSystem<S>
56+
where
57+
S: StageLabel,
58+
{
59+
fn write(self: Box<Self>, schedule: &mut Schedule) {
60+
schedule.add_system_to_stage(self.stage_label, self.system);
61+
}
62+
}
63+
64+
#[cfg(test)]
65+
mod tests {
66+
use crate::{
67+
prelude::ResMut,
68+
schedule::{
69+
InsertSystem, IntoSystemDescriptor, Schedule, SchedulerCommandQueue, SystemStage,
70+
},
71+
system::Commands,
72+
world::World,
73+
};
74+
75+
#[test]
76+
fn insert_system() {
77+
fn sample_system(mut _commands: Commands) {}
78+
let mut schedule = Schedule::default();
79+
schedule.add_stage("test", SystemStage::parallel());
80+
let mut queue = SchedulerCommandQueue::default();
81+
queue.push(InsertSystem {
82+
system: sample_system.into_descriptor(),
83+
stage_label: "test",
84+
});
85+
queue.apply(&mut schedule, 0);
86+
87+
let stage = schedule.get_stage::<SystemStage>(&"test").unwrap();
88+
assert_eq!(stage.parallel_systems().len(), 1);
89+
}
90+
91+
#[derive(Debug, Default, PartialEq)]
92+
struct TestResource(Option<()>);
93+
94+
#[test]
95+
fn insert_system_from_system() {
96+
fn sample_system(mut commands: Commands) {
97+
commands.insert_system(
98+
|mut res: ResMut<TestResource>| {
99+
res.0 = Some(());
100+
},
101+
"test",
102+
);
103+
}
104+
105+
let mut world = World::default();
106+
world.insert_resource(TestResource(None));
107+
108+
let mut schedule = Schedule::default();
109+
schedule.add_stage("test", SystemStage::parallel());
110+
schedule.add_system_to_stage("test", sample_system);
111+
schedule.run_once(&mut world);
112+
113+
let stage = schedule.get_stage::<SystemStage>(&"test").unwrap();
114+
assert_eq!(stage.parallel_systems().len(), 2);
115+
116+
schedule.run_once(&mut world);
117+
assert_eq!(
118+
world.get_resource::<TestResource>(),
119+
Some(&TestResource(Some(())))
120+
);
121+
}
122+
123+
#[test]
124+
fn insert_with_nested_schedule() {
125+
fn sample_system(mut commands: Commands) {
126+
commands.insert_system(|| {}, "test");
127+
}
128+
129+
let mut world = World::default();
130+
131+
let nested_schedule = Schedule::default();
132+
let mut schedule = Schedule::default();
133+
schedule.add_stage("test", SystemStage::parallel());
134+
schedule.add_system_to_stage("test", sample_system);
135+
schedule.add_stage("nested", nested_schedule);
136+
schedule.run_once(&mut world);
137+
138+
let stage = schedule.get_stage::<SystemStage>(&"test").unwrap();
139+
assert_eq!(stage.parallel_systems().len(), 2);
140+
}
141+
}

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

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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;
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(
34+
rng.gen_range(-400f32..400f32),
35+
rng.gen_range(-400f32..400f32),
36+
0.0,
37+
),
38+
..Default::default()
39+
});
40+
};
41+
commands.insert_system(closure, "slow");
42+
}

0 commit comments

Comments
 (0)