1. 系统概述
1.1 项目目标
实现一个类似 Scratch 的可视化积木编程环境,使用 Rust 语言和 Bevy 游戏引擎构建,支持中文积木,采用 AST 作为核心数据结构,通过声明式 DSL 管理积木定义。
1.2 核心特性
- 中文积木界面
- 拖拽式编程
- 实时执行与预览
- ECS 架构集成
- 可扩展的积木系统
- 项目保存与加载
1.3 技术栈
- 语言: Rust 2021 edition
- 引擎: Bevy 0.12+
- UI: Bevy UI + egui
- 序列化: serde + serde_json
- ECS: Bevy ECS
2. 系统架构
2.1 分层架构
┌─────────────────────────────────────────────┐
│ UI Layer (Bevy UI / egui) │
│ 积木面板 | 工作区 | 属性面板 | 预览窗口 │
├─────────────────────────────────────────────┤
│ Visual Editor Layer │
│ 拖拽系统 | 连接验证 | 积木渲染 | 选择管理 │
├─────────────────────────────────────────────┤
│ DSL Definition Layer │
│ 积木注册表 | 参数定义 | 中文映射 | 模板渲染 │
├─────────────────────────────────────────────┤
│ AST Core Layer │
│ 积木节点 | 树结构 | 序列化 | 遍历 │
├─────────────────────────────────────────────┤
│ Execution Engine Layer │
│ 解释器 | 调度器 | 事件系统 | 并发执行 │
├─────────────────────────────────────────────┤
│ Bevy ECS Runtime │
│ 实体 | 组件 | 系统 | 资源 | 查询 │
└─────────────────────────────────────────────┘2.2 数据流
用户拖拽 → UI事件 → 创建AST节点 → 更新积木树
↓
积木树变更 → 触发验证 → 更新执行计划
↓
执行计划 → 解释器执行 → 更新ECS状态
↓
ECS状态变更 → 渲染更新 → 用户看到结果3. 核心数据结构
3.1 AST 定义
// src/ast/mod.rs
use serde::{Serialize, Deserialize};
use bevy::prelude::*;
/// 积木 ID
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct BlockId(pub u64);
/// 积木节点(AST 节点)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockNode {
pub id: BlockId,
pub block_type: BlockType,
pub params: HashMap<String, Value>,
pub children: Vec<BlockId>,
}
/// 积木类型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum BlockType {
// 事件类
WhenFlagClicked,
WhenKeyPressed { key: KeyCode },
WhenSpriteClicked,
// 运动类
Move { steps: f32 },
Turn { degrees: f32 },
GoTo { x: f32, y: f32 },
Glide { seconds: f32, x: f32, y: f32 },
// 外观类
Say { text: String },
Think { text: String },
Show,
Hide,
ChangeSize { percent: f32 },
// 控制类
Wait { seconds: f32 },
Repeat { times: u32 },
Forever,
If { condition: Condition },
IfElse { condition: Condition },
WaitUntil { condition: Condition },
Stop { stop_type: StopType },
// 变量类
SetVariable { name: String },
ChangeVariable { name: String, delta: f32 },
// 自定义积木
Custom { name: String },
}
/// 值类型
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Value {
Number(f64),
String(String),
Boolean(bool),
List(Vec<Value>),
Sprite(Entity),
Color(Color),
Null,
}
/// 条件表达式
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Condition {
True,
False,
Equals(Box<Value>, Box<Value>),
GreaterThan(Box<Value>, Box<Value>),
LessThan(Box<Value>, Box<Value>),
And(Box<Condition>, Box<Condition>),
Or(Box<Condition>, Box<Condition>),
Not(Box<Condition>),
KeyPressed(KeyCode),
TouchingSprite(Entity),
}
/// 积木树(整个程序)
#[derive(Debug, Clone, Serialize, Deserialize, Resource)]
pub struct BlockTree {
pub nodes: HashMap<BlockId, BlockNode>,
pub root_nodes: Vec<BlockId>,
pub next_id: u64,
}3.2 声明式 DSL 定义
// src/dsl/mod.rs
use std::collections::HashMap;
use bevy::prelude::*;
/// 参数类型
#[derive(Debug, Clone, PartialEq)]
pub enum ParamType {
Number,
String,
Boolean,
Color,
Sprite,
List,
Any,
}
/// 参数定义
#[derive(Debug, Clone)]
pub struct ParamDef {
pub name: &'static str,
pub chinese_name: &'static str,
pub param_type: ParamType,
pub default_value: Value,
pub constraints: Vec<ParamConstraint>,
}
/// 参数约束
#[derive(Debug, Clone)]
pub enum ParamConstraint {
Range(f64, f64),
MinLength(usize),
MaxLength(usize),
NonEmpty,
Positive,
}
/// 积木分类
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum BlockCategory {
Event,
Motion,
Look,
Control,
Variable,
Custom,
}
impl BlockCategory {
pub fn chinese_name(&self) -> &'static str {
match self {
BlockCategory::Event => "事件",
BlockCategory::Motion => "运动",
BlockCategory::Look => "外观",
BlockCategory::Control => "控制",
BlockCategory::Variable => "变量",
BlockCategory::Custom => "自定义",
}
}
}
/// 积木执行器类型
pub type BlockExecutor = fn(&mut ExecutionContext, &HashMap<String, Value>, &[BlockId]) -> Result<(), ExecutionError>;
/// 积木定义
pub struct BlockDef {
pub id: &'static str,
pub category: BlockCategory,
pub chinese_name: &'static str,
pub template: &'static str,
pub description: &'static str,
pub params: Vec<ParamDef>,
pub children_slots: Vec<ChildSlot>,
pub executor: BlockExecutor,
pub shape: BlockShape,
}
/// 子积木槽位
#[derive(Debug, Clone)]
pub struct ChildSlot {
pub name: &'static str,
pub chinese_name: &'static str,
pub slot_type: SlotType,
pub accepts: Vec<BlockCategory>,
}
/// 槽位类型
#[derive(Debug, Clone)]
pub enum SlotType {
Sequence, // 可容纳多个积木
Single, // 只能容纳一个积木
Boolean, // 布尔值积木
Value, // 值积木
}
/// 积木形状
#[derive(Debug, Clone)]
pub enum BlockShape {
Hat, // 帽子形状(事件积木)
Stack, // 堆叠形状(命令积木)
Boolean, // 菱形(布尔积木)
Reporter, // 圆角(值积木)
CShape, // C 形(包含子积木)
}
/// 积木注册表
#[derive(Resource)]
pub struct BlockRegistry {
blocks: HashMap<&'static str, BlockDef>,
by_category: HashMap<BlockCategory, Vec<&'static str>>,
}
impl BlockRegistry {
pub fn new() -> Self {
let mut registry = Self {
blocks: HashMap::new(),
by_category: HashMap::new(),
};
registry.register_default_blocks();
registry
}
pub fn register(&mut self, def: BlockDef) {
self.by_category
.entry(def.category.clone())
.or_default()
.push(def.id);
self.blocks.insert(def.id, def);
}
pub fn get(&self, id: &str) -> Option<&BlockDef> {
self.blocks.get(id)
}
pub fn get_by_category(&self, category: &BlockCategory) -> &[&'static str] {
self.by_category
.get(category)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn all_blocks(&self) -> impl Iterator<Item = &BlockDef> {
self.blocks.values()
}
fn register_default_blocks(&mut self) {
self.register_motion_blocks();
self.register_control_blocks();
self.register_event_blocks();
self.register_look_blocks();
self.register_variable_blocks();
}
fn register_motion_blocks(&mut self) {
self.register(BlockDef {
id: "move",
category: BlockCategory::Motion,
chinese_name: "移动",
template: "移动 {步数} 步",
description: "让角色移动指定步数",
params: vec![
ParamDef {
name: "steps",
chinese_name: "步数",
param_type: ParamType::Number,
default_value: Value::Number(10.0),
constraints: vec![ParamConstraint::Range(0.0, 1000.0)],
}
],
children_slots: vec![],
executor: execute_move,
shape: BlockShape::Stack,
});
self.register(BlockDef {
id: "turn",
category: BlockCategory::Motion,
chinese_name: "转向",
template: "向右转 {角度} 度",
description: "让角色向右旋转指定角度",
params: vec![
ParamDef {
name: "degrees",
chinese_name: "角度",
param_type: ParamType::Number,
default_value: Value::Number(90.0),
constraints: vec![ParamConstraint::Range(-360.0, 360.0)],
}
],
children_slots: vec![],
executor: execute_turn,
shape: BlockShape::Stack,
});
}
fn register_control_blocks(&mut self) {
self.register(BlockDef {
id: "repeat",
category: BlockCategory::Control,
chinese_name: "重复",
template: "重复 {次数} 次",
description: "重复执行包含的积木指定次数",
params: vec![
ParamDef {
name: "times",
chinese_name: "次数",
param_type: ParamType::Number,
default_value: Value::Number(4.0),
constraints: vec![ParamConstraint::Range(0.0, 100.0)],
}
],
children_slots: vec![
ChildSlot {
name: "body",
chinese_name: "循环体",
slot_type: SlotType::Sequence,
accepts: vec![BlockCategory::Motion, BlockCategory::Look, BlockCategory::Control],
}
],
executor: execute_repeat,
shape: BlockShape::CShape,
});
}
// ... 其他积木注册
}
/// 执行上下文
pub struct ExecutionContext {
pub world: World,
pub sprite_entity: Entity,
pub variables: HashMap<String, Value>,
pub stop_requested: bool,
}
impl ExecutionContext {
pub fn new(world: World, sprite_entity: Entity) -> Self {
Self {
world,
sprite_entity,
variables: HashMap::new(),
stop_requested: false,
}
}
pub fn get_sprite_transform(&self) -> Option<&Transform> {
self.world.get::<Transform>(self.sprite_entity)
}
pub fn get_sprite_transform_mut(&mut self) -> Option<Mut<Transform>> {
self.world.get_mut::<Transform>(self.sprite_entity)
}
pub fn execute_children(&mut self, children: &[BlockId], tree: &BlockTree) -> Result<(), ExecutionError> {
for child_id in children {
if self.stop_requested {
break;
}
if let Some(node) = tree.nodes.get(child_id) {
self.execute_node(node, tree)?;
}
}
Ok(())
}
pub fn execute_node(&mut self, node: &BlockNode, tree: &BlockTree) -> Result<(), ExecutionError> {
// 获取积木定义
let registry = self.world.get_resource::<BlockRegistry>().unwrap();
let block_type_str = match &node.block_type {
BlockType::Move { .. } => "move",
BlockType::Turn { .. } => "turn",
BlockType::Repeat { .. } => "repeat",
// ... 映射
_ => return Err(ExecutionError::UnknownBlock),
};
let def = registry.get(block_type_str)
.ok_or(ExecutionError::BlockNotRegistered)?;
// 执行积木
(def.executor)(self, &node.params, &node.children)
}
}
/// 执行错误
#[derive(Debug)]
pub enum ExecutionError {
UnknownBlock,
BlockNotRegistered,
InvalidParams,
SpriteNotFound,
StopRequested,
}
/// 积木执行函数
fn execute_move(ctx: &mut ExecutionContext, params: &HashMap<String, Value>, _children: &[BlockId]) -> Result<(), ExecutionError> {
let steps = params.get("steps")
.and_then(|v| if let Value::Number(n) = v { Some(*n as f32) } else { None })
.ok_or(ExecutionError::InvalidParams)?;
if let Some(mut transform) = ctx.get_sprite_transform_mut() {
let rotation = transform.rotation.to_euler(EulerRot::ZYX).0;
let dx = steps * rotation.cos();
let dy = steps * rotation.sin();
transform.translation.x += dx;
transform.translation.y += dy;
}
Ok(())
}
fn execute_turn(ctx: &mut ExecutionContext, params: &HashMap<String, Value>, _children: &[BlockId]) -> Result<(), ExecutionError> {
let degrees = params.get("degrees")
.and_then(|v| if let Value::Number(n) = v { Some(*n as f32) } else { None })
.ok_or(ExecutionError::InvalidParams)?;
if let Some(mut transform) = ctx.get_sprite_transform_mut() {
transform.rotate(Quat::from_rotation_z(degrees.to_radians()));
}
Ok(())
}
fn execute_repeat(ctx: &mut ExecutionContext, params: &HashMap<String, Value>, children: &[BlockId]) -> Result<(), ExecutionError> {
let times = params.get("times")
.and_then(|v| if let Value::Number(n) = v { Some(*n as u32) } else { None })
.ok_or(ExecutionError::InvalidParams)?;
let tree = ctx.world.get_resource::<BlockTree>().unwrap().clone();
for _ in 0..times {
if ctx.stop_requested {
break;
}
ctx.execute_children(children, &tree)?;
}
Ok(())
}4. 积木 DSL 宏系统
// src/dsl/macros.rs
/// 声明式积木定义宏
#[macro_export]
macro_rules! define_block {
(
$block_id:ident {
category: $category:expr,
name: $chinese_name:expr,
template: $template:expr,
description: $description:expr,
$(params: {
$($param_name:ident: $param_type:expr = $default:expr),*
},)?
$(children: {
$($child_name:ident: $child_type:expr),*
},)?
executor: $executor:expr,
shape: $shape:expr
}
) => {
BlockDef {
id: stringify!($block_id),
category: $category,
chinese_name: $chinese_name,
template: $template,
description: $description,
params: vec![
$($(
ParamDef {
name: stringify!($param_name),
chinese_name: stringify!($param_name),
param_type: $param_type,
default_value: $default,
constraints: vec![],
}
),*)?
],
children_slots: vec![
$($(
ChildSlot {
name: stringify!($child_name),
chinese_name: stringify!($child_name),
slot_type: $child_type,
accepts: vec![],
}
),*)?
],
executor: $executor,
shape: $shape,
}
};
}
/// 使用示例
pub fn create_move_block() -> BlockDef {
define_block! {
move_block {
category: BlockCategory::Motion,
name: "移动",
template: "移动 {步数} 步",
description: "让角色移动指定步数",
params: {
steps: ParamType::Number = Value::Number(10.0)
},
executor: execute_move,
shape: BlockShape::Stack
}
}
}5. ECS 集成
// src/ecs/mod.rs
use bevy::prelude::*;
/// 积木实体组件
#[derive(Component)]
pub struct BlockEntity {
pub block_id: BlockId,
pub position: Vec2,
pub size: Vec2,
pub selected: bool,
}
/// 积木连接关系组件
#[derive(Component)]
pub struct BlockConnection {
pub parent: Option<Entity>,
pub children: Vec<Entity>,
pub slot: Option<String>,
}
/// 精灵组件(对应 Scratch 的角色)
#[derive(Component)]
pub struct SpriteComponent {
pub name: String,
pub variables: HashMap<String, Value>,
pub visible: bool,
pub layer: i32,
}
/// 积木执行状态
#[derive(Component)]
pub struct ExecutionState {
pub current_block: Option<BlockId>,
pub running: bool,
pub highlight: bool,
}
/// 积木系统
pub struct BlockSystemPlugin;
impl Plugin for BlockSystemPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<BlockRegistry>()
.init_resource::<BlockTree>()
.add_systems(Startup, setup_block_system)
.add_systems(Update, (
handle_block_drag,
handle_block_drop,
validate_connections,
update_block_positions,
render_blocks,
execute_program,
).chain());
}
}
/// 处理积木拖拽
fn handle_block_drag(
mut commands: Commands,
mouse_button: Res<Input<MouseButton>>,
windows: Query<&Window>,
mut block_query: Query<(Entity, &mut BlockEntity, &mut Transform)>,
) {
if mouse_button.just_pressed(MouseButton::Left) {
// 检测是否点击了积木
if let Some(window) = windows.get_single().ok() {
if let Some(cursor_pos) = window.cursor_position() {
for (entity, mut block, mut transform) in block_query.iter_mut() {
let block_rect = Rect::from_center_size(
transform.translation.truncate(),
block.size,
);
if block_rect.contains(cursor_pos) {
block.selected = true;
commands.entity(entity).insert(Dragging);
}
}
}
}
}
}
/// 标记拖拽中的组件
#[derive(Component)]
struct Dragging;
/// 处理积木放置
fn handle_block_drop(
mut commands: Commands,
mouse_button: Res<Input<MouseButton>>,
mut block_query: Query<(Entity, &mut BlockEntity, &mut BlockConnection, &Transform), With<Dragging>>,
all_blocks: Query<(Entity, &BlockEntity, &Transform), Without<Dragging>>,
) {
if mouse_button.just_released(MouseButton::Left) {
for (entity, mut block, mut connection, transform) in block_query.iter_mut() {
block.selected = false;
// 找到最近的积木槽位
let mut closest_slot: Option<(Entity, f32)> = None;
for (other_entity, other_block, other_transform) in all_blocks.iter() {
let distance = transform.translation.distance(other_transform.translation);
if distance < 50.0 {
if closest_slot.map_or(true, |(_, d)| distance < d) {
closest_slot = Some((other_entity, distance));
}
}
}
// 更新连接关系
if let Some((parent_entity, _)) = closest_slot {
connection.parent = Some(parent_entity);
}
commands.entity(entity).remove::<Dragging>();
}
}
}
/// 执行程序
fn execute_program(
mut commands: Commands,
mut block_tree: ResMut<BlockTree>,
registry: Res<BlockRegistry>,
sprite_query: Query<(Entity, &SpriteComponent)>,
keyboard: Res<Input<KeyCode>>,
) {
// 检查触发事件
let should_execute = keyboard.just_pressed(KeyCode::Space) ||
keyboard.just_pressed(KeyCode::Return);
if !should_execute {
return;
}
// 为每个精灵执行程序
for (sprite_entity, sprite) in sprite_query.iter() {
let mut context = ExecutionContext::new(
commands.world_mut().clone(),
sprite_entity,
);
// 复制积木树
let tree = block_tree.clone();
// 执行根积木
for root_id in &tree.root_nodes {
if let Some(node) = tree.nodes.get(root_id) {
if let Err(e) = context.execute_node(node, &tree) {
eprintln!("执行错误: {:?}", e);
}
}
}
}
}6. UI 渲染系统
// src/ui/mod.rs
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin};
/// 积木编辑器 UI
pub struct BlockEditorUI;
impl Plugin for BlockEditorUI {
fn build(&self, app: &mut App) {
app.add_plugins(EguiPlugin)
.add_systems(Update, render_block_palette)
.add_systems(Update, render_workspace)
.add_systems(Update, render_block_inspector);
}
}
/// 渲染积木面板
fn render_block_palette(
mut contexts: EguiContexts,
registry: Res<BlockRegistry>,
) {
egui::SidePanel::left("block_palette")
.default_width(200.0)
.show(contexts.ctx_mut(), |ui| {
ui.heading("积木面板");
ui.separator();
// 按分类显示积木
for category in [
BlockCategory::Event,
BlockCategory::Motion,
BlockCategory::Look,
BlockCategory::Control,
BlockCategory::Variable,
] {
ui.collapsing(category.chinese_name(), |ui| {
for block_id in registry.get_by_category(&category) {
if let Some(def) = registry.get(block_id) {
render_block_button(ui, def);
}
}
});
}
});
}
/// 渲染积木按钮
fn render_block_button(ui: &mut egui::Ui, def: &BlockDef) {
let button = egui::Button::new(
egui::RichText::new(def.chinese_name)
.size(14.0)
.color(egui::Color32::WHITE)
)
.fill(match def.category {
BlockCategory::Motion => egui::Color32::from_rgb(76, 151, 255),
BlockCategory::Look => egui::Color32::from_rgb(153, 102, 255),
BlockCategory::Control => egui::Color32::from_rgb(255, 171, 25),
BlockCategory::Event => egui::Color32::from_rgb(255, 191, 0),
BlockCategory::Variable => egui::Color32::from_rgb(255, 102, 26),
BlockCategory::Custom => egui::Color32::from_rgb(255, 102, 102),
})
.corner_radius(5.0);
if ui.add(button).clicked() {
// 创建新积木
create_block_from_def(def);
}
// 显示工具提示
button.on_hover_text(format!("{}\n{}", def.chinese_name, def.description));
}
/// 渲染工作区
fn render_workspace(
mut contexts: EguiContexts,
block_tree: Res<BlockTree>,
) {
egui::CentralPanel::default()
.frame(egui::Frame::none().fill(egui::Color32::from_gray(240)))
.show(contexts.ctx_mut(), |ui| {
// 渲染积木树
for root_id in &block_tree.root_nodes {
if let Some(node) = block_tree.nodes.get(root_id) {
render_block_node(ui, node, &block_tree, 0.0);
}
}
});
}
/// 递归渲染积木节点
fn render_block_node(
ui: &mut egui::Ui,
node: &BlockNode,
tree: &BlockTree,
indent: f32,
) {
let registry = ui.data(|data| {
data.get_temp::<BlockRegistry>(egui::Id::new("block_registry"))
});
if let Some(registry) = registry {
if let Some(def) = registry.get(block_type_to_str(&node.block_type)) {
// 渲染积木
let text = render_template(def.template, &node.params);
ui.horizontal(|ui| {
ui.add_space(indent);
// 积木背景
let frame = egui::Frame::none()
.fill(egui::Color32::from_rgb(76, 151, 255))
.corner_radius(5.0)
.inner_margin(egui::Margin::symmetric(8.0, 4.0));
frame.show(ui, |ui| {
ui.label(
egui::RichText::new(text)
.color(egui::Color32::WHITE)
.size(14.0)
);
});
});
// 渲染子积木
for child_id in &node.children {
if let Some(child) = tree.nodes.get(child_id) {
render_block_node(ui, child, tree, indent + 20.0);
}
}
}
}
}
/// 渲染积木属性面板
fn render_block_inspector(
mut contexts: EguiContexts,
block_query: Query<(&BlockEntity, &BlockNode), With<Selected>>,
) {
egui::SidePanel::right("block_inspector")
.default_width(250.0)
.show(contexts.ctx_mut(), |ui| {
ui.heading("积木属性");
ui.separator();
for (entity, node) in block_query.iter() {
ui.label(format!("积木 ID: {:?}", node.id));
// 显示和编辑参数
for (param_name, param_value) in &node.params {
ui.horizontal(|ui| {
ui.label(param_name);
match param_value {
Value::Number(n) => {
let mut value = *n;
if ui.add(egui::DragValue::new(&mut value)).changed() {
// 更新参数
}
}
Value::String(s) => {
let mut value = s.clone();
if ui.text_edit_singleline(&mut value).changed() {
// 更新参数
}
}
_ => {}
}
});
}
}
});
}
/// 模板渲染函数
fn render_template(template: &str, params: &HashMap<String, Value>) -> String {
let mut result = template.to_string();
for (key, value) in params {
let placeholder = format!("{{{}}}", key);
let value_str = match value {
Value::Number(n) => n.to_string(),
Value::String(s) => s.clone(),
Value::Boolean(b) => b.to_string(),
_ => String::new(),
};
result = result.replace(&placeholder, &value_str);
}
result
}7. 序列化与持久化
// src/serialization/mod.rs
use serde::{Serialize, Deserialize};
use std::fs;
/// 项目文件格式
#[derive(Debug, Serialize, Deserialize)]
pub struct ProjectFile {
pub version: String,
pub sprites: Vec<SpriteData>,
pub block_tree: BlockTree,
pub variables: HashMap<String, Value>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SpriteData {
pub name: String,
pub entity_id: u64,
pub position: (f32, f32),
pub rotation: f32,
pub size: f32,
pub visible: bool,
}
impl ProjectFile {
pub fn save(&self, path: &str) -> Result<(), std::io::Error> {
let json = serde_json::to_string_pretty(self)?;
fs::write(path, json)?;
Ok(())
}
pub fn load(path: &str) -> Result<Self, std::io::Error> {
let json = fs::read_to_string(path)?;
let project = serde_json::from_str(&json)?;
Ok(project)
}
}
/// 自动保存系统
pub fn auto_save_system(
time: Res<Time>,
project: Res<ProjectFile>,
mut last_save: Local<f32>,
) {
// 每 30 秒自动保存
if time.elapsed_seconds() - *last_save > 30.0 {
if let Err(e) = project.save("autosave.json") {
eprintln!("自动保存失败: {}", e);
}
*last_save = time.elapsed_seconds();
}
}8. 性能优化
// src/optimization/mod.rs
/// 积木树优化
pub fn optimize_block_tree(tree: &mut BlockTree) {
// 1. 常量折叠
fold_constants(tree);
// 2. 死代码消除
eliminate_dead_code(tree);
// 3. 循环优化
optimize_loops(tree);
}
/// 常量折叠
fn fold_constants(tree: &mut BlockTree) {
for node in tree.nodes.values_mut() {
if let BlockType::Move { steps } = &mut node.block_type {
// 如果步数是常量,直接计算
*steps = steps.round();
}
}
}
/// 并行执行独立积木
pub fn execute_parallel(
tree: &BlockTree,
context: &mut ExecutionContext,
) -> Result<(), ExecutionError> {
// 分析依赖关系
let dependencies = analyze_dependencies(tree);
// 使用线程池并行执行独立分支
let pool = rayon::ThreadPoolBuilder::new()
.num_threads(4)
.build()
.unwrap();
pool.install(|| {
for root in &tree.root_nodes {
if !dependencies.has_dependencies(root) {
// 独立积木可以并行执行
rayon::spawn(|| {
execute_subtree(tree, *root, context);
});
}
}
});
Ok(())
}9. 测试策略
// tests/block_execution.rs
use bevy::prelude::*;
use block_programming::*;
#[test]
fn test_move_block() {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.init_resource::<BlockRegistry>()
.init_resource::<BlockTree>();
// 创建精灵
let sprite_entity = app.world.spawn((
SpriteComponent {
name: "测试精灵".to_string(),
variables: HashMap::new(),
visible: true,
layer: 0,
},
Transform::default(),
)).id();
// 创建移动积木
let move_block = BlockNode {
id: BlockId(1),
block_type: BlockType::Move { steps: 100.0 },
params: HashMap::from([
("steps".to_string(), Value::Number(100.0))
]),
children: vec![],
};
// 执行
let mut context = ExecutionContext::new(app.world.clone(), sprite_entity);
context.execute_node(&move_block, &BlockTree::default()).unwrap();
// 验证位置变化
let transform = app.world.get::<Transform>(sprite_entity).unwrap();
assert!(transform.translation.x > 0.0);
}
#[test]
fn test_repeat_block() {
// 测试循环积木
}
#[test]
fn test_serialization() {
// 测试序列化
}10. 扩展指南
10.1 添加新积木
// 1. 在 AST 中添加新类型
pub enum BlockType {
// ...
PlaySound { sound_name: String },
}
// 2. 注册积木定义
fn register_sound_blocks(registry: &mut BlockRegistry) {
registry.register(BlockDef {
id: "play_sound",
category: BlockCategory::Look, // 或新建 Sound 分类
chinese_name: "播放声音",
template: "播放声音 {声音名称}",
description: "播放指定的声音",
params: vec![
ParamDef {
name: "sound_name",
chinese_name: "声音名称",
param_type: ParamType::String,
default_value: Value::String("喵".to_string()),
constraints: vec![],
}
],
children_slots: vec![],
executor: execute_play_sound,
shape: BlockShape::Stack,
});
}
// 3. 实现执行函数
fn execute_play_sound(
ctx: &mut ExecutionContext,
params: &HashMap<String, Value>,
_children: &[BlockId],
) -> Result<(), ExecutionError> {
let sound_name = params.get("sound_name")
.and_then(|v| if let Value::String(s) = v { Some(s.clone()) } else { None })
.ok_or(ExecutionError::InvalidParams)?;
// 播放声音
println!("播放声音: {}", sound_name);
Ok(())
}10.2 自定义积木
// 用户自定义积木
#[derive(Debug, Clone)]
pub struct CustomBlock {
pub name: String,
pub params: Vec<CustomParam>,
pub body: Vec<BlockId>,
}
// 自定义积木注册
pub fn register_custom_block(
registry: &mut BlockRegistry,
custom_block: CustomBlock,
) {
let def = BlockDef {
id: Box::leak(custom_block.name.clone().into_boxed_str()),
category: BlockCategory::Custom,
chinese_name: Box::leak(custom_block.name.clone().into_boxed_str()),
template: Box::leak(format!("定义 {} 积木", custom_block.name).into_boxed_str()),
description: "用户自定义积木",
params: custom_block.params.iter().map(|p| ParamDef {
name: Box::leak