第六章:系统调度 - 让混乱变得井然有序
🎯 本章目标
学完这一章,你将能够:
- 理解为什么需要控制系统执行顺序
- 使用.before()、.after()和.chain()控制系统顺序
- 创建和使用SystemSet组织相关系统
- 设计清晰的数据流架构
🏫 学校运营的秩序
想象学校的一天必须按照特定顺序进行:
错误的顺序:
❌ 先上课 → 再开门 → 最后老师到校
❌ 先发成绩单 → 再考试 → 最后批改试卷
正确的顺序:
✅ 开门 → 老师到校 → 学生入校 → 上课
✅ 考试 → 批改试卷 → 统计成绩 → 发成绩单
在Bevy中,系统默认是并行运行的,这意味着执行顺序不可预测。有时候这没问题,但当系统之间有依赖关系时,我们就需要系统调度来确保正确的执行顺序。
🚀 基础项目设置
use bevy::prelude::*;
use std::collections::HashMap;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<SchoolSchedule>()
.init_resource::<ExamResults>()
.init_resource::<SchoolStats>()
// 不同阶段的系统组织
.add_systems(Startup, setup_school)
// 主要的系统调度演示
.add_systems(Update, (
// 第一组:基础输入和时间管理(优先级最高)
(
handle_keyboard_input,
update_school_time,
).chain(), // 确保按顺序执行
// 第二组:学生和教师管理
(
process_student_attendance,
assign_classrooms,
prepare_teaching_materials,
).chain().after(update_school_time), // 在时间更新后执行
// 第三组:教学活动
(
conduct_classes,
handle_exam_taking,
grade_exams,
).chain().after(prepare_teaching_materials),
// 第四组:统计和显示(最后执行)
(
calculate_statistics,
update_displays,
print_daily_report,
).chain().after(grade_exams),
))
.run();
}
📚 基础组件和资源
// 基础组件
#[derive(Component)]
struct Student {
id: u32,
name: String,
grade: u8,
}
#[derive(Component)]
struct Teacher {
id: u32,
name: String,
subject: String,
}
#[derive(Component)]
struct Present; // 出勤标记
#[derive(Component)]
struct Classroom {
number: u8,
capacity: u32,
}
#[derive(Component)]
struct Exam {
subject: String,
max_score: u32,
}
#[derive(Component)]
struct ExamScore {
score: u32,
graded: bool,
}
// 资源
#[derive(Resource)]
struct SchoolSchedule {
current_period: u8,
periods_today: u8,
current_activity: String,
}
impl Default for SchoolSchedule {
fn default() -> Self {
Self {
current_period: 1,
periods_today: 6,
current_activity: "晨会".to_string(),
}
}
}
#[derive(Resource, Default)]
struct ExamResults {
results: HashMap<u32, Vec<u32>>, // 学生ID -> 成绩列表
average_scores: HashMap<String, f32>, // 科目 -> 平均分
}
#[derive(Resource, Default)]
struct SchoolStats {
present_students: u32,
total_exams_graded: u32,
classrooms_assigned: u32,
}
⏰ 第一阶段:输入和时间管理
// 系统1:处理键盘输入
fn handle_keyboard_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut schedule: ResMut<SchoolSchedule>,
) {
if keyboard_input.just_pressed(KeyCode::Space) {
schedule.current_period = (schedule.current_period % schedule.periods_today) + 1;
schedule.current_activity = match schedule.current_period {
1 => "第一节课".to_string(),
2 => "第二节课".to_string(),
3 => "课间操".to_string(),
4 => "第三节课".to_string(),
5 => "午休".to_string(),
6 => "考试时间".to_string(),
_ => "放学".to_string(),
};
println!("🕐 切换到:{} (第{}节)", schedule.current_activity, schedule.current_period);
}
if keyboard_input.just_pressed(KeyCode::KeyR) {
println!("🔄 重置为第一节课");
schedule.current_period = 1;
schedule.current_activity = "第一节课".to_string();
}
}
// 系统2:更新学校时间(必须在输入处理后)
fn update_school_time(
schedule: Res<SchoolSchedule>,
time: Res<Time>,
mut last_update: Local<f32>,
) {
// 这个系统依赖于handle_keyboard_input的输入结果
*last_update += time.delta_seconds();
if *last_update >= 2.0 {
println!("⏰ 当前时段:{}", schedule.current_activity);
*last_update = 0.0;
}
}
👥 第二阶段:学生和教师管理
// 系统3:处理学生出勤(必须在时间确定后)
fn process_student_attendance(
mut commands: Commands,
schedule: Res<SchoolSchedule>,
student_query: Query<Entity, (With<Student>, Without<Present>)>,
mut stats: ResMut<SchoolStats>,
) {
// 这个系统依赖update_school_time确定当前时段
if schedule.current_period == 1 {
let mut present_count = 0;
// 模拟学生到校
for entity in student_query.iter().take(3) {
commands.entity(entity).insert(Present);
present_count += 1;
}
if present_count > 0 {
stats.present_students = present_count;
println!("📝 {}名学生已到校签到", present_count);
}
}
}
// 系统4:分配教室(必须在出勤处理后)
fn assign_classrooms(
schedule: Res<SchoolSchedule>,
mut commands: Commands,
student_query: Query<Entity, With<Present>>,
classroom_query: Query<&Classroom>,
mut stats: ResMut<SchoolStats>,
) {
// 这个系统需要知道有多少学生出勤
if schedule.current_period <= 2 {
let present_students = student_query.iter().count();
let available_classrooms = classroom_query.iter().count();
if present_students > 0 && available_classrooms > 0 {
stats.classrooms_assigned = available_classrooms as u32;
println!("🏫 为{}名学生分配了{}间教室", present_students, available_classrooms);
}
}
}
// 系统5:准备教学材料(必须在教室分配后)
fn prepare_teaching_materials(
schedule: Res<SchoolSchedule>,
teacher_query: Query<&Teacher>,
stats: Res<SchoolStats>,
) {
// 这个系统依赖教室分配的结果
if schedule.current_period <= 4 && stats.classrooms_assigned > 0 {
for teacher in teacher_query.iter() {
println!("📖 {}老师正在准备{}课程材料", teacher.name, teacher.subject);
}
}
}
📖 第三阶段:教学活动
// 系统6:进行课堂教学
fn conduct_classes(
schedule: Res<SchoolSchedule>,
teacher_query: Query<&Teacher>,
student_query: Query<&Student, With<Present>>,
) {
if matches!(schedule.current_period, 1 | 2 | 4) {
let student_count = student_query.iter().count();
let teacher_count = teacher_query.iter().count();
if student_count > 0 && teacher_count > 0 {
println!("👨🏫 正在进行课堂教学:{}名老师教{}名学生",
teacher_count, student_count);
}
}
}
// 系统7:考试进行
fn handle_exam_taking(
mut commands: Commands,
schedule: Res<SchoolSchedule>,
student_query: Query<Entity, (With<Student>, With<Present>, Without<ExamScore>)>,
) {
if schedule.current_period == 6 {
// 为参加考试的学生生成考试成绩
for entity in student_query.iter() {
let score = 60 + (rand::random::<u32>() % 40); // 60-100分
commands.entity(entity).insert((
Exam { subject: "数学".to_string(), max_score: 100 },
ExamScore { score, graded: false },
));
}
let exam_count = student_query.iter().count();
if exam_count > 0 {
println!("📝 {}名学生正在参加数学考试", exam_count);
}
}
}
// 系统8:批改考试(必须在考试完成后)
fn grade_exams(
mut exam_query: Query<(&mut ExamScore, &Exam), Changed<ExamScore>>,
mut results: ResMut<ExamResults>,
mut stats: ResMut<SchoolStats>,
student_query: Query<&Student>,
) {
let mut newly_graded = 0;
for (mut exam_score, exam) in exam_query.iter_mut() {
if !exam_score.graded {
exam_score.graded = true;
newly_graded += 1;
// 记录成绩到结果中
// 这里简化处理,实际应该关联具体学生
results.results.entry(1).or_insert_with(Vec::new).push(exam_score.score);
}
}
if newly_graded > 0 {
stats.total_exams_graded += newly_graded;
println!("✅ 批改了{}份考试,总计已批改{}份",
newly_graded, stats.total_exams_graded);
}
}
📊 第四阶段:统计和显示
// 系统9:计算统计数据(必须在批改完成后)
fn calculate_statistics(
mut results: ResMut<ExamResults>,
exam_query: Query<&ExamScore, With<Exam>>,
) {
if !exam_query.is_empty() {
let scores: Vec<u32> = exam_query.iter().map(|e| e.score).collect();
if !scores.is_empty() {
let average = scores.iter().sum::<u32>() as f32 / scores.len() as f32;
results.average_scores.insert("数学".to_string(), average);
}
}
}
// 系统10:更新显示(必须在统计计算后)
fn update_displays(
schedule: Res<SchoolSchedule>,
stats: Res<SchoolStats>,
results: Res<ExamResults>,
) {
// 这个系统依赖统计数据的计算结果
if let Some(avg_score) = results.average_scores.get("数学") {
if *avg_score > 0.0 {
println!("📊 数学考试平均分:{:.1}分", avg_score);
}
}
}
// 系统11:打印日报(最后执行)
fn print_daily_report(
schedule: Res<SchoolSchedule>,
stats: Res<SchoolStats>,
results: Res<ExamResults>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
// 按P键打印完整报告
if keyboard_input.just_pressed(KeyCode::KeyP) {
println!("\n📋 === 学校日报 ===");
println!("当前时段:{}", schedule.current_activity);
println!("出勤学生:{}人", stats.present_students);
println!("分配教室:{}间", stats.classrooms_assigned);
println!("已批改考试:{}份", stats.total_exams_graded);
if let Some(avg) = results.average_scores.get("数学") {
println!("数学平均分:{:.1}分", avg);
}
println!("================\n");
}
}
🎯 使用SystemSet组织系统
当项目变大时,用SystemSet来组织系统更清晰:
// 定义系统集
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum SchoolSystemSet {
Input, // 输入处理
Setup, // 设置和准备
Teaching, // 教学活动
Evaluation, // 评估统计
Display, // 显示和报告
}
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<SchoolSchedule>()
.init_resource::<ExamResults>()
.init_resource::<SchoolStats>()
// 配置系统集的执行顺序
.configure_sets(Update, (
SchoolSystemSet::Input,
SchoolSystemSet::Setup,
SchoolSystemSet::Teaching,
SchoolSystemSet::Evaluation,
SchoolSystemSet::Display,
).chain())
// 将系统分配到相应的集合
.add_systems(Update, (
// 输入处理集合
(
handle_keyboard_input,
update_school_time,
).in_set(SchoolSystemSet::Input),
// 设置准备集合
(
process_student_attendance,
assign_classrooms,
prepare_teaching_materials,
).in_set(SchoolSystemSet::Setup),
// 教学活动集合
(
conduct_classes,
handle_exam_taking,
grade_exams,
).in_set(SchoolSystemSet::Teaching),
// 评估统计集合
(
calculate_statistics,
).in_set(SchoolSystemSet::Evaluation),
// 显示报告集合
(
update_displays,
print_daily_report,
).in_set(SchoolSystemSet::Display),
))
.add_systems(Startup, setup_school)
.run();
}
fn setup_school(mut commands: Commands) {
println!("🏫 学校系统初始化");
println!("🎮 控制说明:");
println!(" 空格键 - 切换到下一个时段");
println!(" R键 - 重置到第一节课");
println!(" P键 - 打印完整日报");
println!("观察系统按正确顺序执行!\n");
// 创建学生
for i in 1..=5 {
commands.spawn(Student {
id: i,
name: format!("学生{}", i),
grade: 3,
});
}
// 创建老师
commands.spawn(Teacher {
id: 1,
name: "张老师".to_string(),
subject: "数学".to_string(),
});
commands.spawn(Teacher {
id: 2,
name: "李老师".to_string(),
subject: "语文".to_string(),
});
// 创建教室
for i in 1..=3 {
commands.spawn(Classroom {
number: i,
capacity: 30,
});
}
}
🔧 高级调度技巧
1. 条件执行与调度结合
// 只在特定条件下按顺序执行系统
.add_systems(Update, (
system_a,
system_b.after(system_a),
system_c.after(system_b),
).run_if(in_state(GameState::Playing)))
2. 灵活的依赖关系
// 复杂的依赖关系
.add_systems(Update, (
input_system,
(
physics_system,
ai_system,
).after(input_system), // 这两个可以并行
render_system.after(physics_system).after(ai_system), // 等两个都完成
))
3. 系统集的嵌套
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum GameplaySet {
Input,
Logic,
Physics,
}
#[derive(SystemSet, Debug, Clone, PartialEq, Eq, Hash)]
enum PhysicsSet {
Movement,
Collision,
Cleanup,
}
.configure_sets(Update, (
GameplaySet::Input,
GameplaySet::Logic,
GameplaySet::Physics,
).chain())
.configure_sets(Update, (
PhysicsSet::Movement,
PhysicsSet::Collision,
PhysicsSet::Cleanup,
).chain().in_set(GameplaySet::Physics))
⚠️ 常见的调度陷阱
1. 循环依赖
// ❌ 这会导致错误!
.add_systems(Update, (
system_a.after(system_b),
system_b.after(system_a), // 循环依赖!
))
2. 过度细化
// ❌ 过度复杂,难以维护
.add_systems(Update, (
sys1.before(sys2).after(sys3).before(sys4).after(sys5)
))
// ✅ 使用SystemSet更清晰
.configure_sets(Update, (SetA, SetB, SetC).chain())
.add_systems(Update, sys1.in_set(SetA))
3. 忽略并行性
// ❌ 不必要的串行化
.add_systems(Update, (
independent_system_a,
independent_system_b.after(independent_system_a), // 不必要
))
// ✅ 让独立系统并行运行
.add_systems(Update, (
independent_system_a,
independent_system_b, // 可以并行
dependent_system.after(independent_system_a),
))
📊 调度性能优化
- 最小化强制顺序:只对真正有依赖的系统使用.after()/.before()
- 合理分组:将相关系统放在同一个SystemSet中
- 避免不必要的.chain():.chain()会完全串行化系统
🤔 检查理解
- 什么情况下需要控制系统执行顺序?
.chain()
和.after()
的区别是什么?- SystemSet有什么优势?
- 如何避免创建循环依赖?
💪 练习挑战
- 为学校系统添加一个新的”SecuritySet”(安全检查集合)
- 实现一个”午餐供应”系统链:准备→分发→清理
- 创建一个条件执行的系统:只在考试时段运行的监考系统
- 使用嵌套SystemSet组织一个复杂的”体育课”系统
🎯 系统调度设计原则
- 数据流驱动:按照数据的流向安排系统顺序
- 最小约束:只对必要的依赖关系添加约束
- 清晰分层:用SystemSet创建清晰的架构层次
- 文档化:为复杂的调度添加注释说明
下一步
恭喜!你已经掌握了Bevy ECS的核心概念。下一章我们将总结所有学到的内容,并讨论如何选择合适的工具来解决实际问题!