slint-ui / slint

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

Please can you make an example which demonstrate how to scale using ListView #4561

Open abique opened 7 months ago

abique commented 7 months ago

Hi,

I would like to know how to work best with ListView when using slint and rust.

Here is the problem:

I wonder how to best solve that issue with Slint? How and when will be disposed the requested resources?

Additionally, how to work best with large ModelRc? Is it using a vector? What is the complexity if I remove an item in the middle of the array?

Please could you make an example that illustrate this problem and demonstrate the best practices to solve it?

Thank you very much, and if the problem description isn't clear, please let me know and I'll try to clarify it.

Cheers, Alex

abique commented 7 months ago

A simple application to illustrate that problem, could be an application which displays all images within a folder in a list view.

Take a folder that is packed with a lot of images, list the directory, and display each images scaled to 128x128, one image per line.

This will involve disk I/O and image loading+scaling (expensive computation), both can't be done on the main thread.

Enjoy :-)

tronical commented 7 months ago

Suppose you're showing a list of images from the network. If the server is very slow to respond, you'd have to show a list of placeholders, until the data has arrived, right?

Later, when the data arrived, you'd update your model and call row_changed() on the ModelNotify (if you're implementing your own Model).

An extreme way would be to do this entirely lazily, so issue the network request for a certain row only when row_data() is called for it the first time. But that means that scrolling will always show a placeholder first.

So ideally we'd have an additional callback in the Model to allow the ListView to notify when the user scrolls and we anticipate a certain amount of rows to become visible in the near future, so that your model can pre-fetch.

I think it makes sense to have an example for this, yes. I wonder what kind of say web service to use for this that for example provides images (or thumbnails of something).

abique commented 7 months ago

I think images on the web is overkill, loading images from disk is enough, you can add a sleep(1s) to simulate slow disk I/O.

wuanzhuan commented 7 months ago

Can reference my project:https://github.com/wuanzhuan/system_monitor. view the src/ui/events_view.slint and src\event_list.rs. the list synchronously support push_back, remove and read cursor. when has 300k+ items it is fluent to sliding the table_view

image

abique commented 6 months ago

Can reference my project:https://github.com/wuanzhuan/system_monitor. view the src/ui/events_view.slint and src\event_list.rs. the list synchronously support push_back, remove and read cursor. when has 300k+ items it is fluent to sliding the table_view

image

Thank you, but I don't think it is the same problem. Imagine that each row costs you 4 MB of RAM, and 2s to compute. You can't have a vector of 300k rows, you'd need a lazy model instead, something like:

wuanzhuan commented 6 months ago

Can reference my project:https://github.com/wuanzhuan/system_monitor. view the src/ui/events_view.slint and src\event_list.rs. the list synchronously support push_back, remove and read cursor. when has 300k+ items it is fluent to sliding the table_view image

Thank you, but I don't think it is the same problem. Imagine that each row costs you 4 MB of RAM, and 2s to compute. You can't have a vector of 300k rows, you'd need a lazy model instead, something like:

  • prefetch_row(row_index)
  • release_row(row_index)

I don't use the vector. It is a intrusive double linked list. Each row is wrapped in Arc. So can freely push、remove and share. It don't need continuous memory for entire rows. Just optimize the index query for the list

abique commented 6 months ago

I don't use the vector. It is a intrusive double linked list. Each row is wrapped in Arc. So can freely push、remove and share. It don't need continuous memory for entire rows. Just optimize the index query for the list

You're missing the point. Yes an intrusive linked list is an elegant solution for the container, but here the container isn't the issue. The issue is about a single row being heavy to compute and to keep in memory, and a request to have some support for lazy model in order to just compute the rows which are displayed and release the one which aren't anymore.

wuanzhuan commented 6 months ago

I do a test. Only the visible row will call Model 's row_data. So you can load the row's resource when calling the row_data

abique commented 6 months ago

I do a test. Only the visible row will call Model 's row_data. So you can load the row's resource when calling the row_data

But then how do you know which rows aren't displayed anymore and can be released?

wuanzhuan commented 6 months ago

I do a test. Only the visible row will call Model 's row_data. So you can load the row's resource when calling the row_data

But then how do you know which rows aren't displayed anymore and can be released?

Assume the current visible rows's index is 0..10. the totle rows's len is 10001. the 11..10000 can be release. but you need to consider preload according to your situation.

abique commented 6 months ago

I do a test. Only the visible row will call Model 's row_data. So you can load the row's resource when calling the row_data

But then how do you know which rows aren't displayed anymore and can be released?

Assume the current visible rows's index is 0..10. the totle rows's len is 10001. the 11..10000 can be release. but you need to consider preload according to your situation.

Exactly that's why it'd be better to have a LazyModel with prefetch/release support.

wuanzhuan commented 6 months ago

I do a test. Only the visible row will call Model 's row_data. So you can load the row's resource when calling the row_data

But then how do you know which rows aren't displayed anymore and can be released?

Assume the current visible rows's index is 0..10. the totle rows's len is 10001. the 11..10000 can be release. but you need to consider preload according to your situation.

Exactly that's why it'd be better to have a LazyModel with prefetch/release support.

I don't think this belongs to the category of UI. Now the Model only hold the visible rows. it is a lazy yet.

abique commented 5 months ago

I've mitigated it using a bunch of LRU caches and:

   fn row_data(&self, row: usize) -> Option<Self::Data> {
        let result = self.fetch_row_data(row);

        if TITAN.library_results_enable_prefetch {
            for i in 0..TITAN.library_results_prefetch_size {
                if row >= i {
                    let _ = self.fetch_row_data(row - i);
                }

                let _ = self.fetch_row_data(row + i);
            }
        }

        result
    }

I'm not sure if I'm entirely happy with that solution, I believe that it'd be better if managed by the widget.