Skip to content

动态读写

有了反射能力(derive(Reflect))和类型目录(TypeRegistry),第三块拼图是在运行时读写字段。这是反射最实际的价值:Scene 加载时把文件里的值写进组件、Inspector 面板让你拖滑条改属性、BRP 远程修改实体数据——本质上都是同一套操作。

通过字符串名读写字段

derive(Reflect) 为结构体生成了 get_field::<T>(name)get_field_mut::<T>(name) 方法,可以直接按字段名拿到具体类型的引用。当你不确定字段类型,或者想用泛型逻辑处理任意字段时,走 trait 方法更灵活:

rust
let mut monster = Monster {
        name: "Goblin".into(),
        hp: 30,
        speed: 5.0,
    };

    if let ReflectMut::Struct(s) = monster.reflect_mut() {
        if let Some(hp) = s.field_mut("hp") {
            if let Some(hp_val) = hp.try_downcast_mut::<i32>() {
                *hp_val -= 10;
            }
        }
    }
    println!("受伤后 HP: {}", monster.hp);

reflect_mut() 返回 ReflectMut 枚举,匹配 Struct variant 后拿到 &mut dyn Struct——可以用 field_mut("hp") 拿到字段的 &mut dyn PartialReflect,再 try_downcast_mut::<i32>() 转回具体类型。

apply:用动态值打补丁

apply 是反射的"写入"原语:把一个 dyn PartialReflect 的值"涂"到另一个 Reflect 值上。对于结构体,它按字段名匹配——只覆盖同名且同类型的字段,忽略其余:

rust
let mut patch = DynamicStruct::default();
    patch.insert("hp", 999i32);
    patch.insert("speed", 99.0f32);

    monster.apply(&patch);
    print_fields("补丁后", &monster);

DynamicStruct 是运行时构造的"匿名结构体"——它实现了 StructPartialReflect,可以当补丁用。Scene 系统加载 .scn 文件时,反序列化出来的就是 DynamicStruct,然后 apply 到真实组件上。

这个机制的妙处是部分更新:补丁里只写了 hpspeedname 字段不受影响。这正是 Scene 和 BRP 能"只修改部分属性"的底层原理。

to_dynamic:快照

to_dynamic() 把一个具体类型的值转成对应的动态表示——结构体变 DynamicStruct,列表变 DynamicList,枚举变 DynamicEnum

rust
let snapshot = monster.to_dynamic();
    println!("快照是 dynamic: {}", snapshot.is_dynamic());
    println!("快照 represent: {:?}", snapshot.get_represented_type_info().map(|i| i.type_path()));

动态值保留了原始类型的信息(get_represented_type_info()),所以你可以用它做序列化、网络传输,或者存下来以后 apply 回去。is_dynamic() 返回 true 表明这是一个运行时构造的值,而非具体类型。

嵌套类型的反射

反射是递归的——如果你的结构体字段本身也是 Reflect 类型,反射会自动穿透进去:

rust
#[derive(Reflect)]
struct Transform2D {
    translation: Vec2,
    rotation: f32,
}

#[derive(Reflect)]
struct Sprite {
    transform: Transform2D,
    color: Color,
    z_index: i32,
}
rust
let sprite = Sprite {
        transform: Transform2D {
            translation: Vec2::new(100.0, 200.0),
            rotation: 0.5,
        },
        color: Color::srgb(1.0, 0.0, 0.0),
        z_index: 10,
    };

    if let ReflectRef::Struct(outer) = sprite.reflect_ref() {
        let transform = outer.field("transform").unwrap();
        if let ReflectRef::Struct(inner) = transform.reflect_ref() {
            let tx = inner.field("translation").unwrap();
            println!("translation = {tx:?}");
        }
    }

apply 同样支持嵌套——补丁里的 transform 字段本身是一个 DynamicStruct,它会递归地应用到 Transform2D 上:

rust
let mut enemy = Sprite {
        transform: Transform2D {
            translation: Vec2::ZERO,
            rotation: 0.0,
        },
        color: Color::WHITE,
        z_index: 0,
    };

    let mut patch_transform = bevy::reflect::structs::DynamicStruct::default();
    patch_transform.insert("translation", Vec2::new(50.0, 50.0));

    let mut patch = bevy::reflect::structs::DynamicStruct::default();
    patch.insert_boxed("transform", Box::new(patch_transform));
    patch.insert("z_index", 100i32);

    enemy.apply(&patch);
    println!("enemy.translation = {:?}", enemy.transform.translation);
    println!("enemy.z_index = {}", enemy.z_index);

标准容器类型(Vec<T>HashMap<K,V>Option<T>)也都实现了 Reflect。VecList 种类,HashMapMap 种类——它们的 apply 语义各不相同:List 按索引逐元素应用,Map 按键匹配:

rust
let mut items: Vec<i32> = vec![1, 2, 3];
    let mut patch_list = bevy::reflect::list::DynamicList::default();
    patch_list.push(10i32);
    patch_list.push(20i32);
    patch_list.push(30i32);
    items.apply(&patch_list);
    println!("items = {items:?}");

运行两个 listing 看效果:

console
cargo run -p ch31-reflect --example listing-31-03
cargo run -p ch31-reflect --example listing-31-04

下一节把这些能力串起来:看 Scene、Inspector、Remote 为什么全都依赖反射。