Mohan S
Mohan S

Reputation: 11

Repeater model very slow when copying an item

We are facing a performance issue in our project. We are using Qt/QML and C++ backend and we are working on the Grid Buttons. If we are changing or updating the data model the repeater section takes more delay to update all the buttons so UI is very slow. Based on the number of buttons the delay gets increased, if 64 buttons are updated means it took 6-sec to load.

when we try to update the data or copy an array of objects into the model container (data Model) of Repeater, it consumes more time to copy all items. For e.g., 30 objects consuming 2 sec. If object counts are increased, then the time consumed also increases proportionally.

As per console debug print, After executing this line console.log("GRID update data model"), it takes 2 sec to execute the next command.

How to avoid this delay in repeater?

Note: this delay issue happened in target device only. In local compilation did not see this delay.

Code:

 function handleModelChanged() {
        delegateModel.model = areaViewModel;
        const newUiType = areaViewModel.uiType
        if (newUiType !== uiType || !modelDataIsEqual(delegateModel, dataModel) ) {
            var buttons = [] 

            for (var row = 0; row < delegateModel.model.rowCount(); row++) {
                var item = delegateModel.items.get(row).model;
                var button = dataModelItemToButton(item);
                
                buttons.push(button);
            }
            console.log("GRID clear data model")
            dataModel = []
            console.log("GRID change uiType " + uiType + " -> " + newUiType)
            uiType = newUiType
            console.log("GRID update data model")
            dataModel = buttons;
            console.log("GRID buttons changed uiType=" + uiType + " cls=" + areaViewModel.callClass);
             
        }  
    }
    Repeater 
    {
        id: areaRepeater
        model: dataModel

        delegate: {
                gridButton;
            }
        onItemAdded: {              
            if (index == areaRepeater.count - 1) {
                console.log("GRID repeater added " + areaRepeater.count + " buttons")
                updateItems()
                }
            }
        }
    }


function modelDataIsEqual(modelData, data) {
        if (!modelData || !data || modelData.model.rowCount() !== data.length)
            return false;
        for (var i = 0; i < modelData.model.rowCount(); ++i) {
            const item = modelData.items.get(i).model;
            const button = data[i];
            if (button.dataAreaShortName !== item.dataAreaShortName ||
                    button.dataAreaName !== item.dataAreaName ||
                    button.dataIsExitArea !== item.dataIsExitArea ||
                    button.dataAreaGridCols !== item.dataAreaGridCols ||
                    button.dataIsGroupedButton !== item.dataIsGroupedButton ||
                    button.dataAreaLocked !== item.dataAreaLocked ||
                    button.dataAreaIcon !== item.dataAreaIcon ||
                    button.dataIsDopArea !== item.dataIsDopArea ||
                    button.dataIsSideGroup !== item.dataIsSideGroup ||
                    dlaButtonStyle !== areaModel.combineBtnStyle ||
                    oddEvenBtnAppearance !== areaModel.getLockedButtonAppearance() ||
                    button.dataButtonAppearance !== item.dataButtonAppearance ||
                    button.dataCrowdedArea !== item.dataCrowdedArea ||
                    button.dataOverCrowdedArea !== item.dataOverCrowdedArea ||
                    button.dataPeopleIconArea  !== item.dataPeopleIconArea ||
                    button.dataSideStateIconArea !== item.dataSideStateIconArea)
            {
                return false;
            }
        }
        return true;
    }
function dataModelItemToButton(item) {
        if (!item)
            return null;
        return {
            dataAreaShortName: item.dataAreaShortName,
            dataAreaName: item.dataAreaName,
            dataIsExitArea: item.dataIsExitArea,
            dataAreaGridCols: item.dataAreaGridCols,
            dataIsGroupedButton: item.dataIsGroupedButton,
            dataAreaLocked: item.dataAreaLocked,
            dataAreaIcon: item.dataAreaIcon,
            dataIsDopArea: item.dataIsDopArea,
            dataIsSideGroup: item.dataIsSideGroup,
            dataButtonAppearance : item.dataButtonAppearance,
            dataCrowdedArea: item.dataCrowdedArea,
            dataOverCrowdedArea: item.dataOverCrowdedArea,
            dataPeopleIconArea : item.dataPeopleIconArea,
            dataSideStateIconArea : item.dataSideStateIconArea
        }
    }

As I mentioned above, larger number of buttons means delay also increased. Like 100ms took for enter one button into model. Somewhere, mentioned use ListModel in Repeater for loading the dataitem. But i tried that one also, seems same delay was happened. I have checked in my target device most of the delay causing to execute this line only dataModel = buttons; I couldn't ignore this line, Since, once copied into the datamodel then only see all the buttons.Any other way to reduce this delay?

Upvotes: 0

Views: 239

Answers (1)

Stephen Quan
Stephen Quan

Reputation: 26299

It appears that you're using a Javascript array as your model.

    delegateModel.model = areaViewModel;

This means every time you have a change, the model must be replaced with a new Javascript array that includes the change. However, the Repeater will have to redraw all records even those that it has seen before. The complexity of this pattern is O(N(N+1)/2) or, roughly O(N^2). The performance can be seen from this table:

Size Total Draw Time
1 1
2 1 + 2
3 1 + 2 + 3
N N ( N + 1 ) / 2

This is bad since it's an exponential delay.

On some platforms, you may not notice this because either N is low or you have a fast enough processor. So, what you do, is you deploy that as an acceptable solution for that platform. However, after a while, when that code becomes production code, your users will increase N beyond your testing limits and your users will raise it as an issue for you to solve.

The preferred way is to refactor and replace with either (1) ListModel, (2) QAbstractListModel, or (3) new the Qt6.4+ list syntax. These models work well because they implement change detection so that the Repeater only has to refresh the newer records not refresh all the records.

    // e.g.
    Repeater {
        model: ListModel {
            id: areaViewModel
        }
    }

In short, do not use a Javascript array as the model for your Repeater.

Upvotes: 0

Related Questions