6个版本 (3个破坏性更新)
0.12.0 | 2023年11月10日 |
---|---|
0.11.0 | 2023年11月4日 |
0.10.2 | 2023年10月25日 |
0.9.0 | 2023年8月30日 |
#881 in 游戏开发
每月下载量31次
在 4 crates 中使用
240KB
4K SLoC
cuicui_chirp
cuicui_chirp
定义了一个基于文本的bevy场景描述的文件格式。
它用于 cuicui
的UI,但可以描述任何类型的场景。
它包括
- 文件格式的解析器。
- 一个bevy加载器,用于在bevy中加载这些文件,包括
loader::Plugin
。 - 一个特质 (
ParseDsl
),用于将您的自定义类型的方法用作chirp方法 - 一个宏来自动实现此特质 (
parse_dsl_impl
)
语法非常接近于 cuicui_dsl
的 dsl!
宏,增加了 一些功能。
何时使用 cuicui_chirp
?
- 您想要一个强大且可扩展的场景定义格式,用于替换bevy的内置
cmds.spawn(…).insert(…).with_children(…)
操作。 - 您希望使用热重载和有用的错误消息来实现快速迭代时间。
- 您希望最大限度地减少编写rust代码以管理场景的数量。
- 您希望使用可重用的场景定义格式。
请注意,cuicui_chirp
由于其本质,不是一个小的依赖项。如果依赖项的大小对您很重要,请考虑使用cuicui_dsl
。
此外,截至0.10
,cuicui_chirp
不支持WASM用于图像和字体资源。
如何使用cuicui_chirp
?
Cargo功能
fancy_errors
(默认):以美观的格式打印解析错误信息。macros
(默认):定义并导出parse_dsl_impl
宏load_font
(默认):将Handle<Font>
作为方法参数加载load_image
(默认):将Handle<Image>
作为方法参数加载more_unsafe
:将一些运行时检查转换为不安全的假设。从理论上讲,这是合理的,但根据我的口味,cuicui_chirp
还没有经过足够的测试,以默认方式做出这些假设。
用法
cuicui_chirp
读取以.chirp
结尾的文件。要加载.chirp
文件,请使用以下ChirpBundle
# #[cfg(feature = "doc_and_test")] mod test {
# use cuicui_chirp::__doc_helpers::*; // ignore this line pls
use bevy::prelude::*;
use cuicui_chirp::ChirpBundle;
fn setup(mut cmds: Commands, assets: Res<AssetServer>) {
cmds.spawn((Camera2dBundle::default(), LayoutRootCamera));
cmds.spawn(ChirpBundle::from(assets.load("my_scene.chirp")));
}
# }
但是,您需要添加加载插件(loader::Plugin
),这样才能正常工作。插件参数化于DSL类型。DSL类型需要实现ParseDsl
特质。
以下是一个使用cuicui_layout_bevy_ui
的DSL的示例
# #[cfg(feature = "doc_and_test")] mod test {
# use cuicui_chirp::__doc_helpers::*; // ignore this line pls
# fn setup() {}
use bevy::prelude::*;
use cuicui_layout_bevy_ui::UiDsl;
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
cuicui_chirp::loader::Plugin::new::<UiDsl>(),
))
.add_systems(Startup, setup)
.run();
}
# }
文档
在
chirp
文件中可用的方法是所选DSL类型中可用的方法(在这种情况下,将是UiDsl
方法)。检查您使用的相应DSL类型的文档页面。所有接受&mut self
的方法都是候选项。
DSL特定文档
括号内的标识符是ParseDsl
上的方法。
由于chirp
格式是ParseDsl
的包装,请参考您添加的ParseDsl
impl上的方法。
使DslBundle
与cuicui_chirp
兼容
让我们重新使用cuicui_dsl
中的示例,并将其扩展到与cuicui_chirp
一起工作。
我们有一个实现了 DslBundle
的 MyDsl
,现在我们还需要为其实现 ParseDsl
。这样就可以在 ParseDsl
中访问方法,使用 parse_dsl_impl
属性宏,并将其添加到所有 DSL 方法定义的 impl
块中
font_size: f32,
}
+#[cuicui_chirp::parse_dsl_impl]
impl MyDsl {
pub fn style(&mut self, style: Style) {
self.style = style;
是的,简单情况下就是这样。如果你想要利用热重载,请确保方法内部不要引发恐慌。
.chirp
文件格式
基本语法类似于 cuicui_dsl
的 dsl!
宏。
一个主要区别是,代码块被替换为一个函数注册表。你可以使用 WorldHandles
资源来注册一个函数。注册的函数对所有使用 cuicui_chirp
加载的 chirp 文件都是全局的。
其他区别还包括增加了导入语句(use
)、模板定义(fn
)和模板调用(template!()
)。
导入语句
目前尚未实现,请继续下一节。
草案设计
注意 导入尚未实现
在 cuicui_chirp
中,你不仅限于单个文件。你可以 导入 其他 chirp 文件。
要这样做,请使用导入语句。导入语句 是文件中的第一个语句;它们以 use
关键字开始,后跟要导入文件的源路径和一个可选的 "as
imported_name",这是在此文件中引用导入的名称。
use different/file
// ...
你有两种使用导入的方式
- 作为 整个文件导入。你可以导入任何文件,并直接使用它,就像使用不带参数的模板一样。如果你想在不同的文件中编写多个复杂的菜单,这很有用。
- 作为 模板集合。你可以导入文件中定义的个别模板。只需将路径与
.template_name
完成即可。
类似于 rust,你可以组合导入,但只能组合来自同一文件的模板,因此以下有效
use different/file.template
use different/file.{template1, template2}
// ...
不支持通配符导入。
公开性
然而,为了能够导入模板,你需要将它们标记为 pub
在源模板中。只需在 fn
前面加上 pub
即可。
模板定义
chirp 文件在文件开头允许一系列 fn
定义。一个 fn
定义看起来非常类似于 rust 函数定义。它有一个名称和零个或多个参数。它们的主体是单个语句
// file: <scene.chirp>
// template name
// ↓
// vvvvvv
fn spacer() {
Spacer(height(10px) width(10%) bg(coral))
}
// parameter
// template name ↓
// ↓ ↓
// vvvvvv vvvvvvvvvvv
fn button(button_text) {
Entity(named(button_text) width(95%) height(200px) bg(purple) row) {
ButtonText(text(button_text) rules(0.5*, 0.5*))
}
}
你可以像调用 rust 宏一样调用模板,通过编写模板名称后跟 !
和括号来调用
# fn sys(cmds: &mut cuicui_dsl::EntityCommands) { cuicui_dsl::dsl!(cmds,
// file: <scene.chirp> (following)
Menu(screen_root row bg(darkgrey)) {
TestSpacer(width(30%) height(100px) bg(pink))
spacer!()
button!("Hello world")
}
# )}
当模板被调用时,它将被替换为定义该模板的 fn
定义主体中的单个根语句。
模板附加功能
模板调用可以跟随着 模板附加功能。
# fn sys(cmds: &mut cuicui_dsl::EntityCommands) { cuicui_dsl::dsl!(cmds,
// file: <scene.chirp> (following)
Menu(screen_root row bg(darkgrey)) {
TestSpacer(width(30%) height(100px) bg(pink))
// Additional method list after the template arguments list
// vvvvvvvvvvvvvvvvvvvvvv
spacer!()(width(50%) bg(coral))
// Both additional methods and additional children added after the argument list
// vvvvvvvvvv
button!("Hello world")(column) {
MoreChildren(text("Hello"))
MoreChildren(text("World"))
}
}
# )}
附加方法将被添加到模板根语句方法列表的末尾。而附加子语句将被添加为模板根语句的子语句。
以这个 chirp 文件为例
fn deep_trailing2(line, color) {
Trailing2Parent {
Trailing2 (text(line) bg(color) width(1*))
}
}
fn deep_trailing1(line2, line1) {
deep_trailing2!(line1, red) {
Trailing1 (text(line2) bg(green) width(2*))
}
}
deep_trailing1!("Second line", "First line") (column bg(beige) rules(1.1*, 2*) margin(20)) {
Trailing0 (text("Third line") bg(blue))
}
它等同于
# fn sys(cmds: &mut cuicui_dsl::EntityCommands) { cuicui_dsl::dsl!(cmds,
Trailing2Parent(column bg(beige) rules(1.1*, 2*) margin(20)) {
Trailing2 (text("First line") bg(red) width(1*))
Trailing1 (text("Second line") bg(green) width(2*))
Trailing0 (text("Third line") bg(blue))
}
# )}
参数替换
注意 “参数”在这里可能指两件事:(1)传递给模板的值,在
template!(foo_bar)
中,foo_bar
是一个参数。(2)传递给 方法 的参数,在Entity(text(method_argument))
中,method_argument
是一个方法参数。在
fn
名称中括号内声明的名称是一个 参数。在fn button(button_text)
中,button_text
是一个模板参数。
当调用模板时,fn
的主体将插入到调用处,传递给模板的参数将内联在模板主体语句中。
请特别注意参数的内联方式
- 参数仅在 方法参数 中内联
- 参数不在引号内 内联
- 参数仅在 它们是整个参数 时内联
❗ 兼容性通知 ❗ |
---|
将来,参数将在更多上下文中允许使用
|
为了避免痛苦的破坏性更改,避免将参数命名为 DSL 方法或模板的名称。 |
# fn sys(cmds: &mut EntityCommands) { dsl!(cmds,
fn button(button_text) {
// Will spawn an entity without name, with tooltip set to whatever
// was passed to `button!`.
Entity(tooltip(button_text) width(95%) height(200px) bg(purple) row) {
// Will spawn an entity named "button_text" with text "button_text"
button_text(text("button_text") rules(0.5*, 0.5*))
// Current limitation:
// `gizmo` method will be called with `GizmoBuilder(button_text)` as first
// argument and whatever was passed to `button!` as second argument
Gizmo(gizmo(GizmoBuilder(button_text), button_text) rules(0.5*, 0.5*))
}
}
# )}
技巧和窍门
请参阅 专门的文档页面 了解 parse_dsl_impl
上所有可用的配置选项。
性能
请考虑显式依赖于 log
和 tracing
包,并启用这些包的 "release_max_level_debug"
功能,以便从发布构建中省略日志消息。
cuicui_chirp
包含在非常热的循环中大量跟踪日志。 "release_max_level_debug"
将在编译时移除跟踪日志,这不仅会使代码更快(否则将读取锁原子的代码),还使 更多优化、内联和循环展开成为可能。
首先,使用以下命令找到依赖树中 log
和 tracing
包的版本
cargo tree -p log -p tracing
然后,将它们添加到您的 Cargo.toml
并启用一个 max_level
功能。请注意,它们已经在您的依赖树中,所以这样做没有缺点
log = { version = "<version found with `cargo tree`>", features = ["release_max_level_debug"] }
tracing = { version = "<version found with `cargo tree`>", features = ["release_max_level_debug"] }
# Note: I recommend `release_max_level_warn` instead.
# `debug` is specific for performance regarding `cuicui_chirp`
下次您编译您的游戏时,您可能需要重新编译整个依赖树,因为 tracing
和 log
通常相当深入。
继承
还记得来自 cuicui_dsl
的继承技巧吗?parse_dsl_impl
与之兼容。使用 delegate
参数指定要委托 MyDsl
实现中找不到的方法的字段。
// pub struct MyDsl<D = ()> {
// #[deref]
// inner: D,
// }
#[parse_dsl_impl(delegate = inner)]
impl<D: DslBundle> MyDsl<D> {
// ...
}
ReflectDsl
与 cuicui_dsl
不同,可以使用 Reflect
来定义 DSL。有关详细信息,请参阅 ReflectDsl
文档。
自定义解析器
由于 .chirp
文件是文本格式,我们需要将文本转换为方法参数。根据类型的不同,parse_dsl_impl
解析方法参数的方式也不同。
请参阅 parse_dsl_impl::type_parsers
以获取详细信息。
cuicui_dsl
和 cuicui_chirp
之间的关系是什么?
cuicui_dsl
是一个宏(dsl!
),而 cuicui_chirp
是一个场景文件格式、解析器和 bevy 加载器。在 cuicui_dsl
的基础上构建 cuicui_chirp
,并且具有与 cuicui_dsl
不同的功能。以下是功能矩阵
功能 | cuicui_dsl |
cuicui_chirp |
---|---|---|
语句与方法 | ✅ | ✅ |
带有内联 rust 代码的 code 块 |
✅ | |
调用已注册函数的 code |
✅ | |
fn 模板 |
rust | ✅ |
从其他文件导入 | rust | |
热重载 | ✅ | |
基于反射的方法 | ✅ | |
颜色、规则的特殊语法 | ✅ | |
轻量级 | ✅ | |
允许使用非 Reflect 组件 |
✅ |
您可以结合使用 cuicui_dsl
和 cuicui_chirp
,这两个 crate 都填补了不同的空白。
依赖项
~23–62MB
~1M SLoC