slint icon indicating copy to clipboard operation
slint copied to clipboard

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

Open abique opened this issue 1 year ago • 13 comments

Hi,

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

Here is the problem:

  • I have a very large array (possibly 10K+ items)
  • In order to not exhaust my system, I'll only have one attribute in that list: int item-id
  • the list will create an ItemWidget component for each entries, binding the item-id property
  • ItemWidget will have to fetch multiple information which can't be loaded and computed from the main thread:
    • images
    • result of expensive calculation
    • database lookup

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 avatar Feb 06 '24 23:02 abique

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 :-)

abique avatar Feb 07 '24 22:02 abique

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).

tronical avatar Feb 22 '24 14:02 tronical

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.

abique avatar Feb 22 '24 14:02 abique

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

wuanzhuan avatar Feb 26 '24 15:02 wuanzhuan

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)

abique avatar Feb 26 '24 17:02 abique

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

wuanzhuan avatar Feb 26 '24 23:02 wuanzhuan

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.

abique avatar Feb 27 '24 08:02 abique

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

wuanzhuan avatar Feb 27 '24 09:02 wuanzhuan

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?

abique avatar Feb 27 '24 09:02 abique

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.

wuanzhuan avatar Feb 27 '24 09:02 wuanzhuan

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.

abique avatar Feb 27 '24 09:02 abique

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.

wuanzhuan avatar Feb 27 '24 09:02 wuanzhuan

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.

abique avatar Apr 19 '24 18:04 abique