2 个版本
0.1.1 | 2023 年 11 月 27 日 |
---|---|
0.1.0 | 2023 年 11 月 26 日 |
#51 in #bevy-ecs
87KB
2K SLoC
bevy-salo
bevy_salo (SAveLOad) 是一个为 bevy_ecs 提供基于 ECS 的序列化的 crate。
独特功能
- 不依赖于反射或 bevy_app。
- 更大的用户控制。
- 按名称匹配实体。
- 自定义的 ser/de 方法,可以加载资源、生成实体等。
入门指南
要开始使用,请注册插件和所有需要序列化的类型。
// It is recommanded to alias here.
type All = bevy_salo::All<SerdeJson>;
app.add_plugins(
SaveLoadPlugin::new::<All>()
.register::<Unit>()
.register::<Weapon>()
.register::<Stat>()
.register::<Hp>()
);
泛型类型(遗憾的是)需要单独注册。
SaveLoadPlugin::new::<All>()
.register::<Unit<Human>>()
.register::<Unit<Monster>>()
All
序列化所有实体,可以通过标记组件来缩小作用域
#[derive(Debug, Default, Component)]
pub struct SaLo;
impl bevy_salo::MarkerComponent for SaLo {
// Set the serialization method here.
type Method = SerdeJson;
}
app.add_plugins(
SaveLoadPlugin::new::<SaLo>()
.register::<Unit>()
);
用法
bevy_salo
创建序列化和反序列化的调度。如果您可以访问一个 &mut World
,您可以使用这些扩展方法。您可以使用系统或实现自定义 Command
。
world.load_from_file::<All>("test.ron");
world.save_to_file::<All>("test.json");
world.deserialize_from::<All>(bytes);
let bytes = world.serialize_to::<All>();
反序列化不会删除现有项目。为了清理,请选择最适合您用例的这些函数之一,或者编写您自己的逻辑。
// remove all serialized components, does not despawn entities
world.remove_serialized_components::<All>();
// despawn entities with a marker.
world.despawn_with_marker::<Marker>();
特质
为了让您的结构体与 bevy_salo
一起工作,您需要实现三个特质之一: SaveLoadCore
、SaveLoadMapped
和 SaveLoad
。
SaveLoadCore
SaveLoadCore
可以很容易地应用于任何实现 serde::Serialize
和 serde::Deserialize
的结构体。
struct Weapon {
name: String,
damage: f32,
cost: i32,
}
impl SaveLoadCore for Weapon {}
但是,您几乎总是应该覆盖 SaveLoadCore
上的 type_name
函数,因为默认实现 Any::type_name()
在 Rust 版本和命名空间之间是不稳定的,这可能在重构时破坏保存格式。
impl SaveLoadCore for Weapon {
// This has to be unique across all registered types.
fn type_name() -> Cow<'static, str> {
Cow::Borrowed("weapon")
}
// Provide a path name for the associated entity.
fn path_name(&self) -> Option<Cow<'static, str>> {
Some(self.name.clone().into())
}
}
SaveLoadMapped
SaveLoadMapped
与 SaveLoadCore
类似,但是您可以将不可序列化的结构映射到可序列化的。
SaveLoad
实现 SaveLoad
允许您在序列化和反序列化过程中执行任意操作。查看其文档以获取更多信息。
字符串池示例
interned_enum!(ElementsServer, Elements: u64 {
Water, Earth, Fire, Air
});
impl SaveLoad for Elements {
type Ser<'ser> = &'ser str;
type De = String;
type Context<'w, 's> = Res<'w, ElementsServer>;
type ContextMut<'w, 's> = ResMut<'s, ElementsServer>;
fn to_serializable<'t, 'w, 's>(&'t self,
_: Entity,
_: impl Fn(Entity) -> EntityPath,
res: &'t Res<'w, ElementsServer>
) -> Self::Ser<'t> {
res.as_str(*self)
}
fn from_deserialize<'w, 's>(
de: Self::De,
_: &mut Commands,
_: Entity,
_: impl FnMut(&mut Commands, &EntityPath) -> Entity,
res: &mut ResMut<'s, ElementsServer>
) -> Self {
res.get(&de)
}
}
路径
bevy_salo
将每个实体记录为其实体 ID 或其路径。实体 ID 仅用于区分,而路径允许与现有实体进行匹配。
每个组件可以可选地使用上述 traits 中定义的 path_name
函数为其关联的实体提供名称。对于非序列化实体,可以使用 PathName
组件代替。
在此示例中,实体的路径名为 "John"
。
Entity {
Character => Some("John"),
Weapon => None,
Armor => None,
}
如果名称冲突,则会引发 panic。
Entity {
Character => Some("John"),
Role => Some("Protagonist"),
}
一个实体的路径包含其所有命名的祖先。考虑这个实体
(root)::window::(unnamed)::characters::John::weapon
武器的路径是 characters::John::weapon
,而其未命名的祖先之前的内容将被忽略。当你想要将 "John"
插入到现有的实体 "characters"
中时,这非常有用。
路径化实体必须具有唯一的路径,但允许重复的名称。
// legal, although both named `weapon`, paths are different
characters::John::weapon
characters::Jane::weapon
// illegal, 2 entities with path `characters`
characters::John::weapon
characters::Jane::(unnamed)::characters
警告
在序列化时,序列化子实体的非序列化父实体必须命名。
// legal, parent is root
(root)::[Named]
// legal, parent is named
Named::[Named]
// illegal, parent is not named, cannot deserialize correctly
(unnamed)::[Named]
PathName
不会被序列化,不应在非静态序列化实体中使用。
依赖
~21MB
~398K SLoC