Type System

Understanding the Live DSL type system helps with writing and debugging Live code effectively.

LiveValue Types

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}

These special variants in LiveValue represent the types supported in Live DSL:

1Component = {
2    // Basic type usage
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    // Resource dependencies
11    icon: dep("crate://self/icons/home.svg"),  // Dependency
12
13    // Expressions
14    scale: (base_size * 2),   // ExprBinOp
15    visible: (!hidden),       // ExprUnOp
16
17    // Enums
18    status: Active,           // BareEnum
19    point: Point(10, 20),     // TupleEnum
20    user: User {              // NamedEnum
21        name: "John",
22        age: 30
23    },
24}

Here are explanations of some key types:

  • Str(&'static str): Static string reference, determined at compile time, stored in program binary
  • String(Arc<str>): Dynamic string, wrapped in Arc smart pointer for shared ownership
  • InlineString: Short string optimization, stored inline without heap allocation

The compiler automatically selects the most appropriate string representation based on context and usage scenario.

1// After string decoding, choose appropriate LiveValue type based on length
2if let Some(v) = decode_str(data, &mut o)? {
3    let value = if let Some(inline_str) = InlineString::from_str(v) {
4        // Use InlineString if string is short enough
5        LiveValue::InlineString(inline_str)
6    } else {
7        // Otherwise use 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,  // Fixed size on stack
16    text: Arc<String>, // Shared heap memory
17    class: &'static str, // Zero cost
18}
  • Root: Represents the root node of Live document, includes id_resolve for identifier resolution
  • Class: Used for component class definition, includes:
    • live_type: Component type information
    • class_parent: Reference to parent class
    • design_info: Additional design-time information
  • Close: Indicates the end of a block, used with Object/Array
  • Object: Indicates the start of an object
  • Array: Indicates the start of an array
  • DSL: Used for embedding Domain Specific Languages, such as shader code
    • token_start/count: Marks position of DSL code in token stream
    • expand_index: Related to macro expansion

1. Module System

  • use: Used for importing other modules, contains import information
  • pub: Used for exporting public components, contains export information

1. Special Value Handling

  • Clone: Copies an existing component, includes:
    • clone: ID of component being copied
    • design_info: Design-time information
  • Deref: Dereferences a component reference, includes:
    • live_type: Component type
    • clone: Component ID
    • design_info: Design-time information
1llive_design!{
2    // Import other modules
3    import makepad_widgets::button::Button; // Generates Import variant
4
5    // LiveValue::Class creates a component
6    App = {{App}} {
7        ui: <Root> {
8            flow: Right,
9            // Object variant starts
10            buttons: {
11                save_button: <Button> {
12                    text: "Save"
13                    onClick: Save
14                }
15                cancel_button: <Button> {
16                    text: "Cancel"
17                }
18            } // Close variant ends
19
20            // Array variant example
21            colors: [
22                #f00,
23                #0f0,
24                #00f
25            ]
26        }
27    }
28
29    // Define components under root node
30    MyButton = <Button> {
31        // Button's type info will be compiled to LiveValue::Deref
32        width: 100
33        height: 40
34        draw_bg: {color: #f00}
35    }
36
37    // Clone existing component
38    DefaultButton = <Button>{}  // Generates Clone variant
39
40    // Use dereferencing
41    ButtonRef = <ButtonBase> {  // Generates Deref variant
42        walk: {width: Fill}
43    }
44}

Further Reading: Serialization and Deserialization Format

Makepad Live values go through serialization and deserialization at runtime. For this, Makepad uses CBOR (Concise Binary Object Representation) format.

CBOR has several key features that make it particularly suitable for Makepad:

1. Compact Encoding

1// CBOR type info and length usually need only 1 byte
2const CBOR_UTF8_START: u8 = 0x60;  // String (0-23 bytes)
3const CBOR_ARRAY_START: u8 = 0x80; // Array (0-23 elements)
4const CBOR_MAP_START: u8 = 0xa0;   // Map (0-23 key-value pairs)
5
6// Example
7let value = "ok";
8// CBOR: [0x62, b'o', b'k']  // 3 bytes
9// JSON: "\\\\"ok\\\\""          // 4 bytes

2. Self-describing and Type-safe

1// CBOR natively supports multiple data types
2impl LiveValue {
3    fn to_cbor(&self) -> Vec<u8> {
4        match self {
5            LiveValue::Int64(i) => [0x1b, /* 8-byte integer */],
6            LiveValue::Float64(f) => [0xfb, /* 8-byte float */],
7            LiveValue::Bool(true) => [0xf5],
8            LiveValue::Bool(false) => [0xf4],
9            LiveValue::String(_) => [0x60 + len, /* UTF8 bytes */],
10            // ...
11        }
12    }
13}

3. Incremental Parsing Capability

1// CBOR can be parsed in a streaming fashion
2fn parse_cbor(data: &[u8], offset: &mut usize) -> Result<LiveValue> {
3    match data[*offset] {
4        tag if tag >= CBOR_UTF8_START => {
5            // Directly get string length
6            let len = (tag - CBOR_UTF8_START) as usize;
7            *offset += 1;
8            // Only parse needed portion
9            let str_data = &data[*offset..*offset + len];
10            *offset += len;
11            Ok(LiveValue::String(str_data))
12        }
13        // ...
14    }
15}

4. Binary-friendly

1// Supports direct byte operations
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// Supports zero-copy
9fn decode_str<'a>(data: &'a [u8]) -> &'a str {
10    // Create string reference directly from byte slice
11    std::str::from_utf8(&data[start..end]).unwrap()
12}

5. Good Extensibility

1// CBOR supports custom tags
2const CBOR_TAG_DATE_TIME: u64 = 0;
3const CBOR_TAG_BIGNUM: u64 = 2;
4const CBOR_TAG_CUSTOM: u64 = 27; // Makepad custom tag
5
6// Can add custom types
7fn encode_custom_type(&self) -> Vec<u8> {
8    let mut data = vec![];
9    data.push(CBOR_TAG_CUSTOM);
10    // ... encode custom data
11    data
12}

6. Performance Advantages

1// 1. Quick type checking
2fn is_string(byte: u8) -> bool {
3    byte >= CBOR_UTF8_START && byte <= CBOR_UTF8_END
4}
5
6// 2. Efficient length retrieval
7fn get_length(byte: u8) -> usize {
8    (byte - CBOR_UTF8_START) as usize  // Single byte operation
9}
10
11// 3. Direct memory access
12fn get_bytes(data: &[u8], o: &mut usize) -> &[u8] {
13    let slice = &data[*o..*o + len];
14    *o += len;
15    slice
16}

7. Suitable for Hot Reloading

1// 1. Easy to compare changes
2fn detect_changes(old: &[u8], new: &[u8]) -> Changes {
3    // CBOR format facilitates byte-by-byte comparison
4}
5
6// 2. Supports partial updates
7fn apply_patch(original: &mut [u8], patch: &[u8]) {
8    // Can update only changed portions
9}

In summary, CBOR provides Makepad with:

  1. Efficient serialization/deserialization
  2. Good extensibility
  3. Features suitable for hot reloading
  4. Binary-friendly operations
  5. Self-describing type system

Imports and Resource Dependencies

Importing other Live modules

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

Resource dependencies

1MyComponent = {
2    // Reference project resources
3    icon: dep("crate://self/resources/icons/home.svg"),
4
5    // Reference external package resources
6    theme: dep("crate://makepad-widgets/resources/theme.json")
7
8    // Reference external fonts
9    // <https://github.com/lxgw/LxgwWenKai>
10    // LXGW WenKai
11    TEXT_LXG_BOLD_FONT = {
12        font_size: (FONT_SIZE_SUB),
13        font: {path: dep("crate://makepad-widgets/resources/LXGWWenKaiBold.ttf")}
14    }
15}