#bevy #ui #layout

cuicui_dsl

一个易于扩展的宏,用于在bevy中创建对象

14个版本 (9个破坏性更新)

0.12.0 2023年11月10日
0.10.2 2023年10月25日
0.8.1 2023年7月20日

#1519游戏开发

每月31次下载
5 个crate中使用

MIT/Apache

35KB
239

cuicui_dsl

The Book Documentation

cuicui_dsl 是一个crate,它公开了一个特例 (DslBundle) 和一个宏 (dsl!),用于在Rust代码中定义bevy场景。

它在 cuicui 中用于UI,但也可以用于任何类型的场景。

何时使用 cuicui_dsl

  • 你希望在bevy中有一个 极其轻量级 但功能强大的场景定义DSL,以替换内置的 cmds.spawn().insert().with_children() 操作。
  • 你不在乎每次更改场景时都必须重新编译整个游戏。

如何使用 cuicui_dsl

  1. 定义一个实现了 DslBundle 的类型
  2. 在这个类型上定义具有 &mut self 接收器的方法
  3. dsl! 宏中使用该类型的这些方法
# use cuicui_dsl::macros::__doc_helpers::*; // ignore this line pls
# use std::borrow::Cow;
use cuicui_dsl::{dsl, DslBundle, EntityCommands};

// DslBundle requires Default impl
#[derive(Default)]
pub struct MyDsl {
    style: Style,
    bg_color: Color,
    font_size: f32,
    inner: BaseDsl,
}
impl MyDsl {
    pub fn named(&mut self, name: impl Into<Cow<'static, str>>) {
        self.inner.named(name);
    }
    pub fn style(&mut self, style: Style) {
        self.style = style;
    }
    pub fn bg_color(&mut self, bg_color: Color) {
        self.bg_color = bg_color;
    }
    pub fn font_size(&mut self, font_size: f32) {
        self.font_size = font_size;
    }
}
impl DslBundle for MyDsl {
    fn insert(&mut self, cmds: &mut EntityCommands) {
        cmds.insert(self.style.clone());
        cmds.insert(BackgroundColor(self.bg_color));
        self.inner.insert(cmds);
        // ...
    }
}
// Now you can use `MyDsl` in a `dsl!` macro
fn setup(mut cmds: Commands) {
    let height = px(32);
    dsl! {
        <MyDsl>
        &mut cmds.spawn_empty(),
        // The uppercase name at the start of a statement is the entity name.
        Root(style(Style { flex_direction: FlexDirection::Column, ..default()}) bg_color(Color::WHITE)) {
            Menu(style(Style { height, ..default()}) bg_color(Color::RED))
            Menu(style(Style { height, ..default()}) bg_color(Color::GREEN))
            Menu(style(Style { height, ..default()}) bg_color(Color::BLUE))
        }
    };
}

文档

dsl! 宏中可用的方法与所选DSL类型中可用的方法相同(在这种情况下,将是 MyDsl 方法)。请查看相应类型的文档页面。所有接受 &mut self 的方法都是候选方法。

这听起来有点啰嗦,因为你应该使用 cuicui_layout 而不是bevy的本地布局算法(flexbox)进行布局 :)

docs.rs页面 已经对 dsl! 宏有广泛的文档,有很多示例

简要来说

dsl! 接受三个参数

  1. (可选) 你想用作 DSL 构建器的 DslBundle 类型。
  2. 用于将场景实例化的 &mut EntityCommands
  3. 一个单条语句

什么是语句?语句是

  • 一个 EntityName(它是一个单个标识符)后面跟
    • 括号内的几个方法
    • 花括号内的几个子语句
    • 以上两者均可

语句创建所选 DslBundle 类型的 Default::default()。然后,对所选的 DslBundle 类型调用括号内的每个方法。最后,使用所构建的 DslBundle 上的 DslBundle::insert 方法生成一个实体。生成的实体具有 Name 组件,设置为提供给 EntityName 的标识符。

如果在大括号内指定了子语句,则将子添加到该实体。

还是不明白?我鼓励你查看 示例 或查看以下文档

DSL 特定文档

由于 dsl! 只是对方法调用的包装,因此你可以参考 docs.rs 页面上的 DslBundle 实现文档,以了解你用于 dsl! 的实现。

技巧和窍门

幕后

dsl! 宏基本上是将命令式顺序 API 转换为声明式函数式 API 的方法。

当你编写

# use cuicui_dsl::macros::__doc_helpers::*; // ignore this line pls
use cuicui_dsl::dsl;
# fn sys(mut cmds: EntityCommands) {
dsl! {
    <BlinkDsl>
    &mut cmds,
    Root {
        FastBlinker(frequency(0.5))
        SlowBlinker(amplitude(2.) frequency(3.0))
    }
}
# }

dsl! 宏会将其转换为

# use cuicui_dsl::macros::__doc_helpers::*; // ignore this line pls
# fn sys(mut cmds: EntityCommands) {
let mut root = BlinkDsl::default();
root.named("Root");
root.node(&mut cmds, |cmds| {
    let mut fast_blinker = BlinkDsl::default();
    fast_blinker.named("FastBlinker");
    fast_blinker.frequency(0.5);
    fast_blinker.insert(&mut cmds.spawn_empty());

    let mut slow_blinker = BlinkDsl::default();
    slow_blinker.named("SlowBlinker");
    slow_blinker.amplitude(2.);
    slow_blinker.frequency(3.0);
    slow_blinker.insert(&mut cmds.spawn_empty());
});
# }

DslBundle::insertBlinkDsl 实现负责将其转换为要插入实体的组件集。

有关更多详细信息和方法,请参阅 dsl! 文档。

继承

cuicui 软件包使用一个非常肮脏的技巧来 compose 不同的 DslBundle

使用 DerefMut,你可以同时获取你自定义 DslBundle 的方法和嵌入到你的自定义 DslBundle 中的另一个 DslBundle 的方法(并且这可以递归地工作)。

使用 bevy 的 DerefDerefMut derive 宏来完成此操作

# use cuicui_dsl::macros::__doc_helpers::*; // ignore this line pls
use cuicui_dsl::DslBundle;

// `= ()` means that if not specified, there is no inner DslBundle
#[derive(Default, Deref, DerefMut)]
pub struct MyDsl<D = ()> {
    #[deref]
    inner: D,
    style: Style,
    bg_color: Color,
    font_size: f32,
}
impl<D: DslBundle> DslBundle for MyDsl<D> {
    fn insert(&mut self, cmds: &mut EntityCommands) {
        cmds.insert(self.style.clone());
        // ... other components to insert ...
        // Always call the inner type at the end so that insertion order follows
        // the type declaration order.
        self.inner.insert(cmds);
    }
}
// Both the methods defined on `MyDsl`
// and the provided `D` are available in the `dsl!` macro for `<MyDsl<D>>`

性能

上述技巧的缺点是 DslBundle 的大小。非常大的 DslBundle 倾向于生成大量的机器代码,仅用于在函数中移动和退出。

尝试使用例如 enumsetbitflags 这样的 bitsets crate 来降低您的 DslBundle 的大小,而不是使用 bool 字段。

还可以考虑将一些大型组件如 Style 进行 Box,以避免移动它们的成本。

在您的 DslBundle 中存储动态集的包

如果您和我一样是懒人,您不需要为每个由您的 DslBundle 管理的包/组件添加字段,您可以存储一个如下的包生成器 Vec

# use cuicui_dsl::macros::__doc_helpers::*; // ignore this line pls
use cuicui_dsl::{EntityCommands, DslBundle};

#[derive(Default)]
pub struct MyDynamicDsl(Vec<Box<dyn FnOnce(&mut EntityCommands)>>);

impl MyDynamicDsl {
    pub fn named(&mut self, name: &str) {
        let name = name.to_string();
        self.0.push(Box::new(move |cmds| {cmds.insert(Name::new(name));}));
    }
    pub fn transform(&mut self, transform: Transform) {
        self.0.push(Box::new(move |cmds| {cmds.insert(transform);}));
    }
    pub fn style(&mut self, style: Style) {
        self.0.push(Box::new(move |cmds| {cmds.insert(style);}));
    }
    // ... Hopefully you get the idea ...
}
impl DslBundle for MyDynamicDsl {
    fn insert(&mut self, cmds: &mut EntityCommands) {
        for spawn in self.0.drain(..) {
            spawn(cmds);
        }
    }
}

cuicui_dslcuicui_chirp 之间的关系是什么?

cuicui_dsl 是一个宏(dsl!),而 cuicui_chirp 是一个场景文件格式、解析器和 bevy 加载器。 cuicui_chirp 建立在 cuicui_dsl 之上,并具有与 cuicui_dsl 不同的功能。以下是一个功能矩阵

功能 cuicui_dsl cuicui_chirp
语句和方法
具有内联 Rust 代码的 code
调用注册函数的 code
fn 模板 rust[^1]
从其他文件导入 rust[^2]
热重载
基于反射的方法
颜色、规则的特殊语法
轻量级
允许使用非 Reflect 组件

您可以将 cuicui_dslcuicui_chirp 结合使用,这两个 crate 填补不同的领域。

[^1]: 一个 fn 模板相当于定义一个接受 EntityCommands 并直接使用它调用 dsl! 的函数 \

# use cuicui_dsl::macros::__doc_helpers::*; // ignore this line pls
use cuicui_dsl::{dsl, EntityCommands};

fn rust_template(cmds: &mut EntityCommands, serv: &AssetServer) {
  dsl! {
    cmds,
    Root(screen_root column) {
      Menu(image(&serv.load("menu1.png")))
      Menu(image(&serv.load("menu2.png")))
    }
  }
}

[^2]: 您当然可以从其他文件导入函数并在 Rust 中使用它们。

依赖关系

~18–54MB
~880K SLoC