Concepts

Live language is a domain-specific language (DSL) specifically designed for UI components and styling. Its basic structure is node-based, where each node can contain properties and child nodes.

Live DSL is compiled in real-time at runtime, supporting hot reloading and dynamic updates.

This design is suitable for the following scenarios:

  1. UI systems requiring high dynamism
  2. Development environments needing hot reload capabilities
  3. Scenarios requiring runtime UI modifications

Live DSL Lifecycle

The runtime compilation process of Live DSL roughly follows these steps:

1. Input Phase

The first step involves inputting Live Documents.

1// Live DSL code
2Button = {
3    width: 100,
4    height: 50,
5    text: "Click me"
6}

2. Live Compiler Phase

Live Tokenizer tokenization:

1pub struct TokenWithSpan {
2    pub token: LiveToken,
3    pub span: TextSpan
4}
5
6// Convert code into token stream
7[
8    Token::Ident("Button"),
9    Token::Punct("="),
10    Token::Open(Brace),
11    Token::Ident("width"),
12    Token::Punct(":"),
13    Token::Int(100),
14    ...
15]

Live Parser parsing:

1pub struct LiveParser<'a> {
2    pub token_index: usize,
3    pub file_id: LiveFileId,
4    pub tokens_with_span: Cloned<Iter<'a, TokenWithSpan>>,
5    // ...
6}
7
8// Parse token stream into initial node structure

Live Original stores the raw node data parsed from source code, including:

  • Complete source code mapping information
  • Design-time and edit-time metadata
  • Input for expansion process
  • Support for error location and reporting
1pub struct LiveOriginal {
2    pub nodes: Vec<LiveNode>,           // Parsed node list
3    pub edit_info: Vec<LiveNode>,       // Edit-related metadata nodes
4    pub design_info: Vec<LiveDesignInfo>, // Design phase information
5    pub tokens: Vec<TokenWithSpan>,     // Source code token stream
6}

For example:

1// Source code
2Button = {
3    width: 100,
4    height: 50,
5    label = {
6        text: "Click me"
7    }
8}
9
10// Parsed into LiveOriginal.nodes
11[
12    LiveNode { id: "Button", value: Object },
13    LiveNode { id: "width", value: Int64(100) },
14    LiveNode { id: "height", value: Int64(50) },
15    LiveNode { id: "label", value: Object },
16    LiveNode { id: "text", value: String("Click me") },
17    LiveNode { value: Close },  // End of label
18    LiveNode { value: Close }   // End of Button
19]

LiveOriginal effectively serves as the AST (Abstract Syntax Tree) of Live DSL.

3. Expansion Phase

The expansion phase extends component declarations in the DSL, handling property expansion, inheritance, establishing component relationships, and setting runtime context. This lays the foundation for subsequent rendering and interaction.

For example:

1// Shorthand form
2Button = <Button> {
3    text: "Click me"
4}
5
6// After expansion
7Button = <Button> {
8    text: "Click me",
9    draw_text: {
10      color: #000
11    },
12    draw_bg: {...}
13}

Live Expander:

1pub struct LiveExpander<'a> {
2    pub live_registry: &'a LiveRegistry,
3    pub in_crate: LiveId,
4    pub in_file_id: LiveFileId,
5    pub errors: &'a mut Vec<LiveError>,
6}
7
8// Handles inheritance, references, and other relationships

Live Expanded:

1pub struct LiveExpanded {
2    pub nodes: Vec<LiveNode>,
3}

4. Instantiation Phase

BTreeMap has several characteristics that make it particularly suitable:

  • BTreeMap sorts by key by default, ensuring stability in component initialization, rendering, and destruction order; properly handling parent-child component dependencies; facilitating debugging and logging.
  • BTreeMap's tree structure makes LiveNode data memory layout more compact. Related component data tends to be adjacent in memory, benefiting cache performance and potentially offering better performance than HashMap. BTreeMap typically has higher space utilization than HashMap.
  • BTreeMap's tree structure is more stable, with component additions and removals not causing large-scale memory reallocations, leading to more predictable performance.

Therefore, it's well-suited for Widget Registry:

1pub struct WidgetRegistry {
2    pub map: BTreeMap<LiveType, (LiveComponentInfo, Box<dyn WidgetFactory>)>,
3}

Widget Instance:

1pub trait Widget: WidgetNode {
2    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope);
3    fn draw(&mut self, cx: &mut Cx2d, scope: &mut Scope) -> DrawStep;
4    // ...
5}
6
7pub trait WidgetNode: LiveApply {
8    // Get design-time component instance
9    fn widget_design(&mut self) -> Option<&mut dyn WidgetDesign> {
10        return None;
11    }
12    // Find component by UID
13    fn uid_to_widget(&self, _uid: WidgetUid) -> WidgetRef;
14    // Find components
15    fn find_widgets(&self, _path: &[LiveId], _cached: WidgetCache, _results: &mut WidgetSet);
16    // Calculate layout information
17    fn walk(&mut self, _cx: &mut Cx) -> Walk;
18    // Get area
19    fn area(&self) -> Area;
20    // Redraw
21    fn redraw(&mut self, _cx: &mut Cx);
22}

Widget Node is the basic node in the UI component tree, providing fundamental layout, traversal, and drawing capabilities. It handles component area management and redrawing, supports component lookup and navigation, and serves as the basic requirement for the Widget trait.

A Widget instance can contain multiple Nodes, with each Node responsible for specific low-level functionality. Widgets achieve complete functionality through Node composition.

5. Runtime Interaction

1impl Widget {
2    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
3        // Event handling
4    }
5
6    fn draw(&mut self, cx: &mut Cx2d, scope: &mut Scope) {
7        // Rendering logic
8    }
9}

Widgets implement runtime interaction through three crucial mechanisms: Event, Action, and Scope. Their details will be covered in the events chapter.

Live Node

Live Node can be understood as each AST node after parsing.

1. Basic Node Structure

In code implementation, nodes are represented by the LiveNode structure:

1pub struct LiveNode {
2    pub origin: LiveNodeOrigin,  // Node source information
3    pub id: LiveId,             // Node identifier
4    pub value: LiveValue,       // Node value
5}

2. Node Types (LiveValue)

Nodes can contain multiple types of values:

1pub enum LiveValue {
2    // Basic value types
3    None,
4    Bool(bool),
5    Int64(i64),
6    Float64(f64),
7    Color(u32),
8    String(Arc<String>),
9
10    // Composite types
11    Object,            // Object node
12    Array,            // Array node
13    Clone{...},       // Clone/inheritance node
14    Class{...},       // Class definition node
15
16    // DSL special types
17    DSL{...},         // DSL code block
18    Import(...),      // Import declaration
19}

3. Node Organization

  • Basic nodes
1Button = {  // This is a node
2    width: 100,    // This is also a node
3    height: 50,    // This is also a node
4}
  • Nested nodes
1Window = {                  // Parent node
2    header = {             // Child node
3        title = {          // Grandchild node
4            text: "Hello"  // Leaf node
5        }
6    }
7}
  • Array nodes
1List = {
2    items: [          // Array node
3        {text: "1"},  // Child node 1
4        {text: "2"},  // Child node 2
5        {text: "3"}   // Child node 3
6    ]
7}