类型系统

了解 Live DSL 中的类型系统,有助于编写和调试 Live 代码。

LiveValue 类型

1#[derive(Clone, Debug, PartialEq)]
2pub enum LiveValue {
3    None,
4    // string types
5    Str(&'static str),
6    String(Arc),
7    InlineString(InlineString),
8    Dependency(Arc),
9    Bool(bool),
10    Int64(i64),
11    Uint64(u64),
12    Float32(f32),
13    Float64(f64),
14    Color(u32),
15    Vec2(Vec2),
16    Vec3(Vec3),
17    Vec4(Vec4),
18    Id(LiveId),
19    IdPath(Arc>),
20    ExprBinOp(LiveBinOp),
21    ExprUnOp(LiveUnOp),
22    ExprMember(LiveId),
23    ExprCall {ident: LiveId, args: usize},
24     // enum thing
25    BareEnum (LiveId),
26    // tree items
27    Root{id_resolve:Box>},
28    Array,
29    Expr,// {expand_index: Option},
30    TupleEnum (LiveId),
31    NamedEnum (LiveId),
32    Object,
33    Clone{clone:LiveId, design_info:LiveDesignInfoIndex},
34    Deref{live_type: LiveType, clone:LiveId, design_info:LiveDesignInfoIndex},
35    Class {live_type: LiveType, class_parent: LivePtr, design_info:LiveDesignInfoIndex},
36    Close,
37
38    // shader code and other DSLs
39    DSL {
40        token_start: u32,
41        token_count: u32,
42        expand_index: Option
43    },
44    Import (Box),
45}

LiveValue 中这些特殊的变体代表了 Live DSL 中支持的类型:

1Component = {
2    // 基础类型的使用
3    name: "Component",         // Str
4    enabled: true,            // Bool
5    width: 100,              // Int64
6    height: 50.5,            // Float64
7    color: #ff0000,          // Color
8    position: vec2(10, 20),  // Vec2
9
10    // 资源依赖
11    icon: dep("crate://self/icons/home.svg"),  // Dependency
12
13    // 表达式
14    scale: (base_size * 2),   // ExprBinOp
15    visible: (!hidden),       // ExprUnOp
16
17    // 枚举
18    status: Active,           // BareEnum
19    point: Point(10, 20),     // TupleEnum
20    user: User {              // NamedEnum
21        name: "John",
22        age: 30
23    },
24}

以下是一些重点类型的说明:

字符串相关类型

  • Str(&'static str): 静态字符串引用,编译时确定,存储在程序二进制中
  • String(Arc<str>): 动态字符串,通过 Arc 智能指针包裹实现共享所有权
  • InlineString: 短字符串优化,直接内联存储而不使用堆内存

编译器会根据上下文和使用场景自动选择最合适的字符串表示形式。

1// 解码字符串后,根据长度选择合适的 LiveValue 类型
2if let Some(v) = decode_str(data, &mut o)? {
3    let value = if let Some(inline_str) = InlineString::from_str(v) {
4        // 如果字符串够短,使用 InlineString
5        LiveValue::InlineString(inline_str)
6    } else {
7        // 否则使用 Arc<String>
8        LiveValue::String(Arc::new(v.to_string()))
9    };
10    self.push(LiveNode {id, origin, value});
11}
12
13// example
14struct Button {
15    id: InlineString,  // 栈上固定大小
16    text: Arc<String>, // 共享堆内存
17    class: &'static str, // 零开销
18}

结构组织相关

  • Root: 代表 Live 文档的根节点,包含 id_resolve 来解析标识符
  • Class: 用于定义组件类,包含:
    • live_type: 组件的类型信息
    • class_parent: 父类的引用
    • design_info: 设计时的额外信息
  • Close: 表示一个区块的结束,配合 Object/Array 等使用
  • Object: 表示一个对象的开始
  • Array: 表示一个数组的开始

DSL 相关

  • DSL: 用于嵌入领域特定语言(Domain Specific Language),如着色器代码
    • token_start/count: 标记 DSL 代码在 token 流中的位置
    • expand_index: 宏展开相关

模块系统

  • Import: 用于导入模块,包含模块的路径和内容

特殊值处理

  • Clone: 复制一个已有组件,包含:
    • clone: 被复制组件的 ID
    • design_info: 设计时信息
  • Deref: 解引用一个组件引用,包含:
    • live_type: 组件类型
    • clone: 组件 ID
    • design_info: 设计时信息
1live_design!{
2    // 导入其他模块
3    use link::button::Button; // 生成 Import 变体
4
5	// LiveValue::Class 创建一个组件
6    App = {{App}} {
7        ui: <Root> {
8            flow: Right,
9            // Object 变体开始
10            buttons: {
11                save_button: <Button> {
12                    text: "Save"
13                    onClick: Save
14                }
15                cancel_button: <Button> {
16                    text: "Cancel"
17                }
18            } // Close 变体结束
19
20            // Array 变体示例
21            colors: [
22                #f00,
23                #0f0,
24                #00f
25            ]
26        }
27    }
28
29
30	// 根节点下定义各种组件
31    MyButton = <Button> {
32        // Button 的类型信息会被编译为 LiveValue::Deref
33        width: 100
34        height: 40
35        draw_bg: {color: #f00}
36    }
37
38    // 克隆已有组件
39    DefaultButton = <Button>{}  // 生成 Clone 变体
40
41    // 使用解引用
42    ButtonRef = <ButtonBase> {  // 生成 Deref 变体
43        walk: {width: Fill}
44    }
45
46
47}

扩展阅读:序列化与反序列化格式

Makepad Live 值在运行时会经过序列化和反序列化的过程。为此,Makepad 采用了 CBOR (Concise Binary Object Representation,简明二进制对象表示法) 格式。

CBOR 有几个关键特点使其非常适合 Makepad:

1. 编码紧凑

1// CBOR 的类型信息和长度通常只需要1个字节
2const CBOR_UTF8_START: u8 = 0x60;  // 字符串(0-23字节)
3const CBOR_ARRAY_START: u8 = 0x80; // 数组(0-23个元素)
4const CBOR_MAP_START: u8 = 0xa0;   // 映射(0-23个键值对)
5
6// 示例
7let value = "ok";
8// CBOR: [0x62, b'o', b'k']  // 3字节
9// JSON: "\\"ok\\""            // 4字节

2. 自描述和类型安全

1// CBOR 内置支持多种数据类型
2impl LiveValue {
3    fn to_cbor(&self) -> Vec<u8> {
4        match self {
5            LiveValue::Int64(i) => [0x1b, /* 8字节整数 */],
6            LiveValue::Float64(f) => [0xfb, /* 8字节浮点 */],
7            LiveValue::Bool(true) => [0xf5],
8            LiveValue::Bool(false) => [0xf4],
9            LiveValue::String(_) => [0x60 + len, /* UTF8字节 */],
10            // ...
11        }
12    }
13}

3. 增量解析能力

1// CBOR 可以流式解析
2fn parse_cbor(data: &[u8], offset: &mut usize) -> Result<LiveValue> {
3    match data[*offset] {
4        tag if tag >= CBOR_UTF8_START => {
5            // 直接获取字符串长度
6            let len = (tag - CBOR_UTF8_START) as usize;
7            *offset += 1;
8            // 只需解析需要的部分
9            let str_data = &data[*offset..*offset + len];
10            *offset += len;
11            Ok(LiveValue::String(str_data))
12        }
13        // ...
14    }
15}

4. 二进制友好

1// 支持直接字节操作
2fn read_u16(data: &[u8], o: &mut usize) -> u16 {
3    let val = u16::from_be_bytes([data[*o], data[*o + 1]]);
4    *o += 2;
5    val
6}
7
8// 支持零拷贝
9fn decode_str<'a>(data: &'a [u8]) -> &'a str {
10    // 直接从字节切片创建字符串引用
11    std::str::from_utf8(&data[start..end]).unwrap()
12}

5. 扩展性好

1// CBOR 支持自定义标签
2const CBOR_TAG_DATE_TIME: u64 = 0;
3const CBOR_TAG_BIGNUM: u64 = 2;
4const CBOR_TAG_CUSTOM: u64 = 27; // Makepad自定义标签
5
6// 可以添加自定义类型
7fn encode_custom_type(&self) -> Vec<u8> {
8    let mut data = vec![];
9    data.push(CBOR_TAG_CUSTOM);
10    // ... 编码自定义数据
11    data
12}

6. 性能优势

1// 1. 快速类型检查
2fn is_string(byte: u8) -> bool {
3    byte >= CBOR_UTF8_START && byte <= CBOR_UTF8_END
4}
5
6// 2. 高效的长度获取
7fn get_length(byte: u8) -> usize {
8    (byte - CBOR_UTF8_START) as usize  // 单字节操作
9}
10
11// 3. 直接内存访问
12fn get_bytes(data: &[u8], o: &mut usize) -> &[u8] {
13    let slice = &data[*o..*o + len];
14    *o += len;
15    slice
16}

7. 适合热重载

1// 1. 容易比较变化
2fn detect_changes(old: &[u8], new: &[u8]) -> Changes {
3    // CBOR 格式便于逐字节比较
4}
5
6// 2. 支持部分更新
7fn apply_patch(original: &mut [u8], patch: &[u8]) {
8    // 可以只更新变化的部分
9}

总的来说,CBOR 为 Makepad 提供了:

  1. 高效的序列化/反序列化
  2. 良好的扩展性
  3. 适合热重载的特性
  4. 二进制友好的操作
  5. 自描述的类型系统

导入 与 资源依赖

导入其他 Live DSL 模块

1// 导入单个组件
2use makepad_widgets::base::Button
3
4// 导入整个模块
5use makepad_widgets::theme_desktop_dark::*
6
7// 带别名的导入
8use makepad_widgets::base::Button as CustomButton
9pub CustomButton

资源依赖

1MyComponent = {
2    // 引用项目内的资源
3    icon: dep("crate://self/resources/icons/home.svg"),
4
5    // 引用外部包的资源
6    theme: dep("crate://makepad-widgets/resources/theme.json")
7
8	// 引用外部字体
9    // <https://github.com/lxgw/LxgwWenKai>
10    // 霞鹜文楷
11    TEXT_LXG_BOLD_FONT = {
12        font_size: (FONT_SIZE_SUB),
13        font: {path: dep("crate://makepad-widgets/resources/LXGWWenKaiBold.ttf")}
14    }
15}