0
votes

My goal is to implement a sort of simulator, with high rate data updates.
The application is composed from the following parts:

  • A data model: it stores the data and it is used by a TableView as a model
  • A simulator: a thread which updates the whole data model every 250ms
  • A Qml view: contains a TableView to show the data

As soon as I start the application, I can see the data changing in the TableView, but there is a wierd crash if I try to scroll the TableView using the mouse wheel (keep scrolling for a while).
The only log I get is the following:

ASSERT failure in QList::at: "index out of range", file C:\work\build\qt5_workdir\w\s\qtbase\include/QtCore/../../src/corelib/tools/qlist.h, line 510

The more interesting thing is that I get this crash only in a Windows environment, while in a CentOs machine I do not get any error.

I tried here to extract the main part of my project and to generalize it as much as possibile.
Find below the code, or if you prefer you can download the full project from this link

mydata.h

#ifndef MYDATA_H
#define MYDATA_H

#include <QObject>

class MyData : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int first READ first WRITE setFirst NOTIFY firstChanged)
    Q_PROPERTY(int second READ second WRITE setSecond NOTIFY secondChanged)
    Q_PROPERTY(int third READ third WRITE setThird NOTIFY thirdChanged)

public:

    explicit MyData(int first, int second, int third, QObject* parent=0);

    int first()const {return m_first;}
    int second()const {return m_second;}
    int third()const {return m_third;}

    void setFirst(int v){m_first=v;}
    void setSecond(int v){m_second=v;}
    void setThird(int v){m_third=v;}

signals:
    void firstChanged();
    void secondChanged();
    void thirdChanged();

private:
    int m_first;
    int m_second;
    int m_third;
};

#endif // MYDATA_H

mydata.cpp

#include "mydata.h"

MyData::MyData(int first, int second, int third, QObject* parent) : QObject(parent)
{
    m_first=first;
    m_second=second;
    m_third=third;
}

datamodel.h

#ifndef DATAMODEL_H
#define DATAMODEL_H

#include <QAbstractListModel>
#include <QMutex>
#include "mydata.h"


class DataModel: public QAbstractListModel
{
    Q_OBJECT
public:

    enum DataModelRoles {
        FirstRole = Qt::UserRole + 1,
        SecondRole,
        ThirdRole
    };

    //*****************************************/
    //Singleton implementation:
    static DataModel& getInstance()
            {
                static DataModel    instance; // Guaranteed to be destroyed.
                                      // Instantiated on first use.
                return instance;
            }

    DataModel(DataModel const&) = delete;
    void operator=(DataModel const&)  = delete;
    //*****************************************/

    QList<MyData*>& getData(){return m_data;}

    void addData(MyData* track);

    int rowCount(const QModelIndex & parent = QModelIndex()) const;

    QVariant data(const QModelIndex & index, int role = FirstRole) const;

protected:
    QHash<int, QByteArray> roleNames() const;

private:

    QMutex m_mutex;
    QList<MyData*> m_data;

    DataModel(QObject* parent=0);


};

#endif // DATAMODEL_H

datamodel.cpp

#include "DataModel.h"
#include "QDebug"

DataModel::DataModel(QObject* parent): QAbstractListModel(parent)
{

}

void DataModel::addData(MyData *track)
{
    beginInsertRows(QModelIndex(),rowCount(),rowCount());
    m_data<<track;
    endInsertRows();
}

int DataModel::rowCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent)
    return m_data.size();
}

QVariant DataModel::data(const QModelIndex &index, int role) const
{
    MyData* data=m_data[index.row()];
    switch (role) {
    case FirstRole:
        return data->first();

    case SecondRole:
        return data->second();

    case ThirdRole:
        return data->third();
    default:
        return QVariant();
    }
}



QHash<int, QByteArray> DataModel::roleNames() const
{
    QHash<int, QByteArray> roles;
    roles[FirstRole] = "First";
    roles[SecondRole] = "Second";
    roles[ThirdRole] = "Third";
    return roles;
}

simulator.h

#ifndef SIMULATOR_H
#define SIMULATOR_H

#include <QThread>

class Simulator: public QThread
{
    Q_OBJECT
public:
    Simulator(QObject* parent=0);
    void run() Q_DECL_OVERRIDE;

private:
    void createNewData();
    void updateExistingData();

    int randInt(int from, int to);
};

#endif // SIMULATOR_H

simulator.cpp

#include "simulator.h"
#include <math.h>
#include <mydata.h>
#include <datamodel.h>
Simulator::Simulator(QObject* parent) : QThread(parent)
{
    createNewData();
}

void Simulator::run()
{
    long updateRate=250;
    while(true)
    {
        updateExistingData();
        msleep(updateRate);
    }
}


void Simulator::createNewData()
{
    int numOfData=10000;
    for(int i=0;i<numOfData;i++)
    {
        int first=i;
        int second=randInt(0,1000);
        int third=randInt(0,1000);
        MyData* data=new MyData(first,second,third);
        DataModel::getInstance().addData(data);
    }

}

void Simulator::updateExistingData()
{
    QList<MyData*> list=DataModel::getInstance().getData();
    for(int i=0;i<list.size();i++)
    {
        MyData* curr=list.at(i);
        curr->setSecond(curr->second()+1);
        curr->setThird(curr->third()+2);
        QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
        emit DataModel::getInstance().dataChanged(index,index);    
    }
}

int Simulator::randInt(int from, int to)
{
    // Random number between from and to
    return qrand() % ((to + 1) - from) + from;
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include "simulator.h"
#include "datamodel.h"
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    DataModel& model=DataModel::getInstance();

    Simulator* s=new Simulator();
    s->start();

    QQmlApplicationEngine engine;

    QQmlContext *ctxt = engine.rootContext();
    ctxt->setContextProperty("myModel", &model);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

main.qml

import QtQuick 2.5
import QtQuick.Window 2.2
import QtQuick.Controls 1.4
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.4

Window {
    visible: true
    width: 800
    height: 600

    TableView {
        id: tableView
        width: parent.width
        height: parent.height
        frameVisible: true

        model: myModel
        sortIndicatorVisible: true
        property string fontName: "Arial"

        TableViewColumn {
            id: firstColumn
            title: "First"
            role: "First"
            movable: false
            resizable: false
            width: tableView.viewport.width/3
            delegate: Text{
                font.family: tableView.fontName
                text: styleData.value
                horizontalAlignment: TextInput.AlignHCenter
                verticalAlignment: TextInput.AlignVCenter

            }
        }
        TableViewColumn {
            id: secondColumn
            title: "Second"
            role: "Second"
            movable: false
            resizable: false
            width: tableView.viewport.width/3
            delegate: Text{
                font.family: tableView.fontName
                text: styleData.value
                horizontalAlignment: TextInput.AlignHCenter
                verticalAlignment: TextInput.AlignVCenter

            }
        }
        TableViewColumn {
            id: thirdColumn
            title: "Third"
            role: "Third"
            movable: false
            resizable: false
            width: tableView.viewport.width/3
            delegate: Text{
                font.family: tableView.fontName
                text: styleData.value
                horizontalAlignment: TextInput.AlignHCenter
                verticalAlignment: TextInput.AlignVCenter

            }
        }
    }
}
1

1 Answers

0
votes

I am glad to share with you the solution to my own answers, hoping it could help someone who is getting the same error.

The key point of the problem is here:

void Simulator::updateExistingData()
{
    QList<MyData*> list=DataModel::getInstance().getData();
    for(int i=0;i<list.size();i++)
    {
        MyData* curr=list.at(i);
        curr->setSecond(curr->second()+1);
        curr->setThird(curr->third()+2);
        QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
        emit DataModel::getInstance().dataChanged(index,index);    //ERROR!
    }
}

In fact I am emitting a signal on the DataModel in a thread that is not the Gui thread: this will fall into a concurrent access issue, between the gui thread (which is accessing the data to fill the TableView) and the updater thread (which is accessing the data to update the values).

The solution is to emit the dataChanged signal on the gui thread, and we can do that by using the Qt Signal/Slot mechanism.
Therefore:
In datamodel.h:

public slots:
    void updateGui(int rowIndex);

In datamodel.cpp:

void DataModel::updateGui(int rowIndex)
{
    QModelIndex qIndex=index(rowIndex,0, QModelIndex());
    dataChanged(qIndex,qIndex);
}

In simulator.h:

signals:
    void dataUpdated(int row);

In simulator.cpp:

Simulator::Simulator(QObject* parent) : QThread(parent)
{
    createNewData();
    connect(this,SIGNAL(dataUpdated(int)), &DataModel::getInstance(), SLOT(updateGui(int)));
}

...

void Simulator::updateExistingData()
{
    QList<MyData*> list=DataModel::getInstance().getData();
    for(int i=0;i<list.size();i++)
    {
        MyData* curr=list.at(i);
        curr->setSecond(curr->second()+1);
        curr->setThird(curr->third()+2);
        QModelIndex index=DataModel::getInstance().index(i,0, QModelIndex());
        emit dataUpdated(i);
    }
}

Using the Signal/Slot approach, we are sure that the request will be handled in the receiver class by the thread who have created that class (the Gui thread), therefore the following dataChanged signal will be emitted by the proper thread.