slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
17.58k stars 604 forks source link

Rust compilation of generated code too slow #448

Open AshfordN opened 3 years ago

AshfordN commented 3 years ago

I'm trying to build a calendar UI to familiarize myself with sixtyfps, which incidentally is part of my rust learning experience. My project directory look like the following:

practice/
    build.rs
    src/
        main.rs
    ui/
        main.60

my build.rs file looks like this:

use sixtyfps_build;

fn main() {
    sixtyfps_build::compile("ui/main.60").unwrap();
}

my main.rs file look like this:

use sixtyfps;

sixtyfps::include_modules!();

fn main() {
    Calendar::new().run();
}

and my main.60 file looks like this:

cell := Rectangle {
    property <string> content;
    property <bool> bold;
    background: #FFF;

    HorizontalLayout { 
        padding: 2px;
        alignment: center;
        Text {
            text: root.content;
            vertical-alignment: center;
            color: root.content == 0? #FFF : #444;
            font-weight: bold? 600 : 400;
        }
    }

}

month := VerticalLayout {
    property <string> month-name;
    padding: 5px;

    Rectangle {
        background: #007ACC;

        HorizontalLayout {
            padding: 2px;

            Text {
                text: root.month-name;
                horizontal-alignment: center;
                vertical-alignment: center;
                color: #FFF;
            }
        }
    }

    HorizontalLayout { 
        padding: 1px;
        cell { content: "S";}
        cell { content: "M";}
        cell { content: "T";}
        cell { content: "W";}
        cell { content: "T";}
        cell { content: "F";}
        cell { content: "S";}
    }

    Rectangle {
        background: #B9B9B9;

        GridLayout {
            padding: 1px;
            spacing: 1px;

            cell{content: 0; bold: true; row: 0; col: 0;}
            cell{content: 0; row: 0; col: 1;}
            cell{content: 1; row: 0; col: 2;}
            cell{content: 2; row: 0; col: 3;}
            cell{content: 3; row: 0; col: 4;}
            cell{content: 4; row: 0; col: 5;}
            cell{content: 5; bold: true; row: 0; col: 6;}
            cell{content: 6; bold: true; row: 1; col: 0;}
            cell{content: 7; row: 1; col: 1;}
            cell{content: 8; row: 1; col: 2;}
            cell{content: 9; row: 1; col: 3;}
            cell{content: 10; row: 1; col: 4;}
            cell{content: 11; row: 1; col: 5;}
            cell{content: 12; bold: true; row: 1; col: 6;}
            cell{content: 13; bold: true; row: 2; col: 0;}
            cell{content: 14; row: 2; col: 1;}
            cell{content: 15; row: 2; col: 2;}
            cell{content: 16; row: 2; col: 3;}
            cell{content: 17; row: 2; col: 4;}
            cell{content: 18; row: 2; col: 5;}
            cell{content: 19; bold: true; row: 2; col: 6;}
            cell{content: 20; bold: true; row: 3; col: 0;}
            cell{content: 21; row: 3; col: 1;}
            cell{content: 22; row: 3; col: 2;}
            cell{content: 23; row: 3; col: 3;}
            cell{content: 24; row: 3; col: 4;}
            cell{content: 25; row: 3; col: 5;}
            cell{content: 26; bold: true; row: 3; col: 6;}
            cell{content: 27; bold: true; row: 4; col: 0;}
            cell{content: 28; row: 4; col: 1;}
            cell{content: 29; row: 4; col: 2;}
            cell{content: 31; row: 4; col: 3;}
            cell{content: 0; row: 4; col: 4;}
            cell{content: 0; row: 4; col: 5;}
            cell{content: 0; bold: true; row: 4; col: 6;}
            cell{content: 0; bold: true; row: 5; col: 0;}
            cell{content: 0; row: 5; col: 1;}
            cell{content: 0; row: 5; col: 2;}
            cell{content: 0; row: 5; col: 3;}
            cell{content: 0; row: 5; col: 4;}
            cell{content: 0; row: 5; col: 5;}
            cell{content: 0; bold: true; row: 5; col: 6;}
        }
    }
}

export Calendar := Window {
    title: "Calendar";

    VerticalLayout { 
        padding: 10px;
        alignment: space-between;

        HorizontalLayout { 
            alignment: space-between;
            month {month-name: "January";}
            month {month-name: "February";}
            month {month-name: "March";}
            month {month-name: "April";}
        }

        HorizontalLayout { 
            alignment: space-between;
            month {month-name: "May";}
            month {month-name: "June";}
            month {month-name: "July";}
            month {month-name: "August";}
        }

        HorizontalLayout { 
            alignment: space-between;
            month {month-name: "September";}
            month {month-name: "October";}
            month {month-name: "November";}
            month {month-name: "December";}
        }
    }
}

I have also previewed the UI using the live-preview feature of the VSCode extension. However when I run cargo build, the process hangs indefinitely and uses quite a bit of RAM (>1GB). If I shorten the main.60 file so that it has fewer elements, it manages to complete, even though it still takes quite a while. I've also tested some of the examples and they work fine. It appears as though the compilation time increases exponentially with an increase in UI elements. I'm not sure if my technique or approach is wrong though, but I'm hoping someone could point out the issue here.

Preview as shown by the live-preview: Screenshot from 2021-09-01 02-34-29

My environment: Linux Mint 20.2 Kernel Version 5.4.0-81 Intel i7-4790K 16GB RAM rust version 1.54.0 (using the 2018 edition)

tronical commented 3 years ago

ACK, we end up generating a lot of code that's taking too long to compile :( - in contrary to the interpreter/preview.

ogoffart commented 3 years ago

eventually implementing https://github.com/sixtyfpsui/sixtyfps/issues/40 might help. But this also expose some exponential time in the rust compiler itself.

AshfordN commented 3 years ago

To confirm what @tronical is saying, the .rs file that is generated from the .60 file is ~12MB. So there is probably an upper limit to the number of elements that you can reasonably define in the static UI, above which your compilation time becomes impractically long. The main.60 file above probably creates more than 500 elements. So even though improvements are made that result in more efficient compilations, I'm thinking that after a certain point it would make more sense to use models, to build portions of the UI at runtime. I've restructured my main.60 file to following:

cell := Rectangle {
    property <string> content;
    property <bool> bold;
    background: #FFF;

    HorizontalLayout { 
        padding: 2px;
        alignment: center;
        Text {
            text: root.content;
            vertical-alignment: center;
            color: root.content <= 0? #FFF : #444;
            font-weight: bold? 600 : 400;
        }
    }
}

struct MonthParams := {
    name: string,
    start-day: int,
    num-days: int
}

month := Rectangle {
    property <MonthParams> params;
    callback get-day(int) -> int;
    get-day(idx) => {
        if (idx < params.start-day || idx >= (params.num-days + params.start-day)) { 0 }
        else { (idx + 1) - params.start-day }
    }

    VerticalLayout {
        padding: 5px;

        Rectangle {
            background: #007ACC;

            HorizontalLayout {
                padding: 2px;

                Text {
                    text: root.params.name;
                    horizontal-alignment: center;
                    vertical-alignment: center;
                    color: #FFF;
                }
            }
        }

        HorizontalLayout { 
            padding: 1px;
            for day in ["S", "M", "T", "W", "T", "F", "S"]: cell { content: day;}
        }

        Rectangle {
            background: #B9B9B9;

            HorizontalLayout { 
                padding: 1px;
                spacing: 1px;
                for ci in 7: VerticalLayout {
                    spacing: 1px;
                    for ri in 6: cell{ content: get-day((ri * 7) + ci); }
                }
            }
        }
    }
}

export Calendar := Window {
    property <int> year: 2021;
    callback get-month-params(int, int) -> MonthParams;
    title: "Calendar";

    Rectangle { 
        VerticalLayout { 
            padding: 10px;
            alignment: space-between;

            for row[r-idx] in [[1,2,3,4], [5,6,7,8], [9,10,11,12]]: HorizontalLayout { 
                alignment: space-between;
                for month in row: month { params: get-month-params(month, year); }
            }
        }
    }

}

and my main.rs file now looks like this:

use sixtyfps;

sixtyfps::include_modules!();

fn main() {
    let cal = Calendar::new();
    cal.on_get_month_params(|month, _year| match month {
        1 => MonthParams { name: "January".into(), start_day: 5, num_days: 31 },
        2 => MonthParams { name: "February".into(), start_day: 1, num_days: 28 },
        3 => MonthParams { name: "March".into(), start_day: 1, num_days: 31 },
        4 => MonthParams { name: "April".into(), start_day: 4, num_days: 30 },
        5 => MonthParams { name: "May".into(), start_day: 6, num_days: 31 },
        6 => MonthParams { name: "June".into(), start_day: 2, num_days: 30 },
        7 => MonthParams { name: "July".into(), start_day: 4, num_days: 31 },
        8 => MonthParams { name: "August".into(), start_day: 0, num_days: 31 },
        9 => MonthParams { name: "September".into(), start_day: 3, num_days: 30 },
        10 => MonthParams { name: "October".into(), start_day: 5, num_days: 31 },
        11 => MonthParams { name: "November".into(), start_day: 1, num_days: 30 },
        12 => MonthParams { name: "December".into(), start_day: 3, num_days: 31 },
        _ => MonthParams { name: "".into(), start_day: 0, num_days: 30, },
    });

    cal.run();
}

With this, I'm able to compile everything in under 5 seconds.

tronical commented 3 years ago

After #40 this is a bit better. Looking at the first example (the expanded one without the model), we now generate "only" 2.4 MB sources and on my machine a debug build takes about 3-4 minutes to compile.

qarmin commented 10 months ago

Currently my project generates 10MB rs file and debug compilation takes ~30 seconds(10s build script, 20s rust compilation)

So it is not too good, but also not too bad. I use:

not sure how can I get decrease compilation time