ClassView: improvements of performance for flat tree view

Improved lazy loading mechanism. The main problem here was that
fetchMore function creates children items not only for selected
item but also for children items.
When one changed something in code then whole treeview was rebuilt
(and fetchMore function was called).

Replaced using "contains" and operator[] with 'value'.

Task-number: QTCREATORBUG-8813
Task-number: QTCREATORBUG-8801(partially)
Change-Id: If1ab69a0a67ff828275176ad99c3c63d2a1fa4a2
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent c35ad8c6
......@@ -200,9 +200,9 @@ Manager *Manager::instance()
Checks \a item for lazy data population of a QStandardItemModel.
*/
bool Manager::canFetchMore(QStandardItem *item) const
bool Manager::canFetchMore(QStandardItem *item, bool skipRoot) const
{
return d->parser.canFetchMore(item);
return d->parser.canFetchMore(item, skipRoot);
}
/*!
......@@ -215,6 +215,11 @@ void Manager::fetchMore(QStandardItem *item, bool skipRoot)
d->parser.fetchMore(item, skipRoot);
}
bool Manager::hasChildren(QStandardItem *item) const
{
return d->parser.hasChildren(item);
}
void Manager::initialize()
{
// use Qt::QueuedConnection everywhere
......
......@@ -54,10 +54,12 @@ public:
//! Get an instance of Manager
static Manager *instance();
bool canFetchMore(QStandardItem *item) const;
bool canFetchMore(QStandardItem *item, bool skipRoot = false) const;
void fetchMore(QStandardItem *item, bool skipRoot = false);
bool hasChildren(QStandardItem *item) const;
signals:
void stateChanged(bool state);
......
......@@ -297,7 +297,7 @@ void NavigationWidget::fetchExpandedItems(QStandardItem *item, const QStandardIt
return;
const QModelIndex &parent = d->treeModel->indexFromItem(target);
if (d->ui->treeView->isExpanded(parent))
if (d->ui->treeView->isExpanded(parent) && Manager::instance()->canFetchMore(item, true))
Manager::instance()->fetchMore(item, true);
int itemIndex = 0;
......
......@@ -94,6 +94,8 @@ namespace Internal {
class ParserPrivate
{
public:
typedef QHash<QString, CPlusPlus::Document::Ptr>::const_iterator CitDocumentList;
//! Constructor
ParserPrivate() : flatMode(false) {}
......@@ -147,9 +149,10 @@ public:
CPlusPlus::Document::Ptr ParserPrivate::document(const QString &fileName) const
{
if (!documentList.contains(fileName))
CitDocumentList cit = documentList.find(fileName);
if (cit == documentList.end())
return CPlusPlus::Document::Ptr();
return documentList[fileName];
return cit.value();
}
// ----------------------------- Parser ---------------------------------
......@@ -186,9 +189,9 @@ Parser::~Parser()
Checks \a item for lazy data population of a QStandardItemModel.
*/
bool Parser::canFetchMore(QStandardItem *item) const
bool Parser::canFetchMore(QStandardItem *item, bool skipRoot) const
{
ParserTreeItem::ConstPtr ptr = findItemByRoot(item);
ParserTreeItem::ConstPtr ptr = findItemByRoot(item, skipRoot);
if (ptr.isNull())
return false;
return ptr->canFetchMore(item);
......@@ -207,6 +210,14 @@ void Parser::fetchMore(QStandardItem *item, bool skipRoot) const
ptr->fetchMore(item);
}
bool Parser::hasChildren(QStandardItem *item) const
{
ParserTreeItem::ConstPtr ptr = findItemByRoot(item);
if (ptr.isNull())
return false;
return ptr->childCount() != 0;
}
/*!
Switches to flat mode (without subprojects) if \a flat returns \c true.
*/
......@@ -281,24 +292,18 @@ ParserTreeItem::ConstPtr Parser::parse()
continue;
ParserTreeItem::Ptr item;
if (!d->flatMode)
item = ParserTreeItem::Ptr(new ParserTreeItem());
QString prjName(prj->displayName());
QString prjType(prjName);
if (prj->document())
prjType = prj->projectFilePath().toString();
SymbolInformation inf(prjName, prjType);
item = ParserTreeItem::Ptr(new ParserTreeItem());
QStringList projectList = addProjectNode(item, prj->rootProjectNode());
if (d->flatMode)
addFlatTree(item, prj->rootProjectNode());
else
addProjectNode(item, prj->rootProjectNode());
if (d->flatMode) {
// use prj path (prjType) as a project id
// addProject(item, prj->files(Project::ExcludeGeneratedFiles), prjType);
//! \todo return back, works too long
ParserTreeItem::Ptr flatItem = createFlatTree(projectList);
item.swap(flatItem);
}
item->setIcon(prj->rootProjectNode()->icon());
rootItem->appendChild(item, inf);
}
......@@ -339,9 +344,6 @@ void Parser::addSymbol(const ParserTreeItem::Ptr &item, const CPlusPlus::Symbol
// easy solution - lets add any scoped symbol and
// any symbol which does not contain :: in the name
// if (symbol->isDeclaration())
// return;
//! \todo collect statistics and reorder to optimize
if (symbol->isForwardClassDeclaration()
|| symbol->isExtern()
......@@ -352,17 +354,11 @@ void Parser::addSymbol(const ParserTreeItem::Ptr &item, const CPlusPlus::Symbol
)
return;
// skip static local functions
// if ((!symbol->scope() || symbol->scope()->isClass())
// && symbol->isStatic() && symbol->isFunction())
// return;
const CPlusPlus::Name *symbolName = symbol->name();
if (symbolName && symbolName->isQualifiedNameId())
return;
QString name = d->overview.prettyName(symbol->name()).trimmed();
QString name = d->overview.prettyName(symbolName).trimmed();
QString type = d->overview.prettyType(symbol->type()).trimmed();
int iconType = CPlusPlus::Icons::iconTypeForSymbol(symbol);
......@@ -385,51 +381,25 @@ void Parser::addSymbol(const ParserTreeItem::Ptr &item, const CPlusPlus::Symbol
// prevent showing a content of the functions
if (!symbol->isFunction()) {
const CPlusPlus::Scope *scope = symbol->asScope();
if (scope) {
if (const CPlusPlus::Scope *scope = symbol->asScope()) {
CPlusPlus::Scope::iterator cur = scope->firstMember();
while (cur != scope->lastMember()) {
CPlusPlus::Scope::iterator last = scope->lastMember();
while (cur != last) {
const CPlusPlus::Symbol *curSymbol = *cur;
++cur;
if (!curSymbol)
continue;
// if (!symbol->isClass() && curSymbol->isStatic() && curSymbol->isFunction())
// return;
addSymbol(itemAdd, curSymbol);
}
}
}
bool appendChild = true;
// if item is empty and has not to be added
if (symbol->isNamespace() && itemAdd->childCount() == 0)
appendChild = false;
if (appendChild)
if (!(symbol->isNamespace() && itemAdd->childCount() == 0))
item->appendChild(itemAdd, information);
}
/*!
Creates a flat tree from the list of projects specified by \a projectList.
*/
ParserTreeItem::Ptr Parser::createFlatTree(const QStringList &projectList)
{
QReadLocker locker(&d->prjLocker);
ParserTreeItem::Ptr item(new ParserTreeItem());
foreach (const QString &project, projectList) {
if (!d->cachedPrjTrees.contains(project))
continue;
ParserTreeItem::ConstPtr list = d->cachedPrjTrees[project];
item->add(list);
}
return item;
}
/*!
Parses the project with the \a projectId and adds the documents from the
\a fileList to the project. Updates the internal cached tree for this
......@@ -480,8 +450,9 @@ ParserTreeItem::Ptr Parser::getCachedOrParseProjectTree(const QStringList &fileL
{
d->prjLocker.lockForRead();
ParserTreeItem::Ptr item = d->cachedPrjTrees.value(projectId);
// calculate current revision
if (!projectId.isEmpty() && d->cachedPrjTrees.contains(projectId)) {
if (!projectId.isEmpty() && !item.isNull()) {
// calculate project's revision
unsigned revision = 0;
foreach (const QString &file, fileList) {
......@@ -494,7 +465,7 @@ ParserTreeItem::Ptr Parser::getCachedOrParseProjectTree(const QStringList &fileL
// if even revision is the same, return cached project
if (revision == d->cachedPrjTreesRevision[projectId]) {
d->prjLocker.unlock();
return d->cachedPrjTrees[projectId];
return item;
}
}
......@@ -520,7 +491,7 @@ ParserTreeItem::ConstPtr Parser::getParseDocumentTree(const CPlusPlus::Document:
ParserTreeItem::Ptr itemPtr(new ParserTreeItem());
unsigned total = doc->globalSymbolCount();
const unsigned total = doc->globalSymbolCount();
for (unsigned i = 0; i < total; ++i)
addSymbol(itemPtr, doc->globalSymbolAt(i));
......@@ -547,11 +518,13 @@ ParserTreeItem::ConstPtr Parser::getCachedOrParseDocumentTree(const CPlusPlus::D
const QString &fileName = doc->fileName();
d->docLocker.lockForRead();
if (d->cachedDocTrees.contains(fileName)
&& d->cachedDocTreesRevision.contains(fileName)
&& d->cachedDocTreesRevision[fileName] == doc->revision()) {
ParserTreeItem::ConstPtr item = d->cachedDocTrees.value(fileName);
CitCachedDocTreeRevision citCachedDocTreeRevision = d->cachedDocTreesRevision.find(fileName);
if (!item.isNull()
&& citCachedDocTreeRevision != d->cachedDocTreesRevision.end()
&& citCachedDocTreeRevision.value() == doc->revision()) {
d->docLocker.unlock();
return d->cachedDocTrees[fileName];
return item;
}
d->docLocker.unlock();
return getParseDocumentTree(doc);
......@@ -788,9 +761,10 @@ QStringList Parser::addProjectNode(const ParserTreeItem::Ptr &item,
// our own files
QStringList fileList;
CitCachedPrjFileLists cit = d->cachedPrjFileLists.find(nodePath);
// try to improve parsing speed by internal cache
if (d->cachedPrjFileLists.contains(nodePath)) {
fileList = d->cachedPrjFileLists[nodePath];
if (cit != d->cachedPrjFileLists.end()) {
fileList = cit.value();
} else {
fileList = projectNodeFileList(node);
d->cachedPrjFileLists[nodePath] = fileList;
......@@ -819,5 +793,44 @@ QStringList Parser::addProjectNode(const ParserTreeItem::Ptr &item,
return projectList;
}
QStringList Parser::getAllFiles(const ProjectNode *node)
{
QStringList fileList;
if (!node)
return fileList;
const QString &nodePath = node->path();
CitCachedPrjFileLists cit = d->cachedPrjFileLists.find(nodePath);
// try to improve parsing speed by internal cache
if (cit != d->cachedPrjFileLists.end()) {
fileList = cit.value();
} else {
fileList = projectNodeFileList(node);
d->cachedPrjFileLists[nodePath] = fileList;
}
// subnodes
QList<ProjectExplorer::ProjectNode *> projectNodes = node->subProjectNodes();
foreach (const ProjectExplorer::ProjectNode *project, projectNodes) {
fileList += getAllFiles(project);
}
return fileList;
}
void Parser::addFlatTree(const ParserTreeItem::Ptr &item, const ProjectNode *node)
{
if (!node)
return;
QStringList fileList = getAllFiles(node);
fileList.removeDuplicates();
if (fileList.count() > 0) {
addProject(item, fileList, node->path());
}
}
} // namespace Internal
} // namespace ClassView
......@@ -58,9 +58,10 @@ public:
explicit Parser(QObject *parent = 0);
~Parser();
bool canFetchMore(QStandardItem *item) const;
bool canFetchMore(QStandardItem *item, bool skipRoot = false) const;
void fetchMore(QStandardItem *item, bool skipRoot = false) const;
bool hasChildren(QStandardItem *item) const;
signals:
//! File list is changed
......@@ -93,6 +94,9 @@ protected slots:
void onResetDataDone();
protected:
typedef QHash<QString, unsigned>::const_iterator CitCachedDocTreeRevision;
typedef QHash<QString, QStringList>::const_iterator CitCachedPrjFileLists;
void addProject(const ParserTreeItem::Ptr &item, const QStringList &fileList,
const QString &projectId = QString());
......@@ -115,11 +119,12 @@ protected:
QStringList addProjectNode(const ParserTreeItem::Ptr &item,
const ProjectExplorer::ProjectNode *node);
QStringList getAllFiles(const ProjectExplorer::ProjectNode *node);
void addFlatTree(const ParserTreeItem::Ptr &item,
const ProjectExplorer::ProjectNode *node);
QStringList projectNodeFileList(const ProjectExplorer::FolderNode *node) const;
ParserTreeItem::Ptr createFlatTree(const QStringList &projectList);
private:
//! Private class data pointer
ParserPrivate *d;
......
......@@ -41,7 +41,7 @@
#include <QDebug>
enum { debug = true };
enum { debug = false };
namespace ClassView {
namespace Internal {
......@@ -130,10 +130,8 @@ void ParserTreeItem::copyTree(const ParserTreeItem::ConstPtr &target)
// d_ptr->symbolInformations.reserve(amount);
// every child
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator cur =
target->d->symbolInformations.constBegin();
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator end =
target->d->symbolInformations.constEnd();
CitSymbolInformations cur = target->d->symbolInformations.constBegin();
CitSymbolInformations end = target->d->symbolInformations.constEnd();
for (; cur != end; ++cur) {
ParserTreeItem::Ptr item(new ParserTreeItem());
......@@ -220,9 +218,7 @@ void ParserTreeItem::removeChild(const SymbolInformation &inf)
ParserTreeItem::Ptr ParserTreeItem::child(const SymbolInformation &inf) const
{
if (!d->symbolInformations.contains(inf))
return ParserTreeItem::Ptr();
return d->symbolInformations[inf];
return d->symbolInformations.value(inf);
}
/*!
......@@ -267,16 +263,13 @@ void ParserTreeItem::add(const ParserTreeItem::ConstPtr &target)
// add children
// every target child
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator cur =
target->d->symbolInformations.constBegin();
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator end =
target->d->symbolInformations.constEnd();
CitSymbolInformations cur = target->d->symbolInformations.constBegin();
CitSymbolInformations end = target->d->symbolInformations.constEnd();
while (cur != end) {
const SymbolInformation &inf = cur.key();
const ParserTreeItem::Ptr &targetChild = cur.value();
if (d->symbolInformations.contains(inf)) {
// this item has the same child node
const ParserTreeItem::Ptr &child = d->symbolInformations[inf];
ParserTreeItem::Ptr child = d->symbolInformations.value(inf);
if (!child.isNull()) {
child->add(targetChild);
} else {
......@@ -284,40 +277,6 @@ void ParserTreeItem::add(const ParserTreeItem::ConstPtr &target)
add->copyTree(targetChild);
d->symbolInformations[inf] = add;
}
} else {
ParserTreeItem::Ptr add(new ParserTreeItem());
add->copyTree(targetChild);
d->symbolInformations[inf] = add;
}
// next item
++cur;
}
}
/*!
Subtracts an internal state with \a target, which contains the subtrahend.
*/
void ParserTreeItem::subtract(const ParserTreeItem::ConstPtr &target)
{
if (target.isNull())
return;
// every target child
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator cur =
target->d->symbolInformations.constBegin();
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator end =
target->d->symbolInformations.constEnd();
while (cur != end) {
const SymbolInformation &inf = cur.key();
if (d->symbolInformations.contains(inf)) {
// this item has the same child node
if (!d->symbolInformations[inf].isNull())
d->symbolInformations[inf]->subtract(cur.value());
if (d->symbolInformations[inf].isNull()
|| d->symbolInformations[inf]->childCount() == 0)
d->symbolInformations.remove(inf);
}
// next item
++cur;
}
......@@ -325,11 +284,9 @@ void ParserTreeItem::subtract(const ParserTreeItem::ConstPtr &target)
/*!
Appends this item to the QStandardIten item \a item.
\a recursive does it recursively for the tree items (might be needed for
lazy data population.
*/
void ParserTreeItem::convertTo(QStandardItem *item, bool recursive) const
void ParserTreeItem::convertTo(QStandardItem *item) const
{
if (!item)
return;
......@@ -337,18 +294,17 @@ void ParserTreeItem::convertTo(QStandardItem *item, bool recursive) const
QMap<SymbolInformation, ParserTreeItem::Ptr> map;
// convert to map - to sort it
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator curHash =
d->symbolInformations.constBegin();
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator endHash =
d->symbolInformations.constEnd();
CitSymbolInformations curHash = d->symbolInformations.constBegin();
CitSymbolInformations endHash = d->symbolInformations.constEnd();
while (curHash != endHash) {
map.insert(curHash.key(), curHash.value());
++curHash;
}
typedef QMap<SymbolInformation, ParserTreeItem::Ptr>::const_iterator MapCitSymbolInformations;
// add to item
QMap<SymbolInformation, ParserTreeItem::Ptr>::const_iterator cur = map.constBegin();
QMap<SymbolInformation, ParserTreeItem::Ptr>::const_iterator end = map.constEnd();
MapCitSymbolInformations cur = map.constBegin();
MapCitSymbolInformations end = map.constEnd();
while (cur != end) {
const SymbolInformation &inf = cur.key();
ParserTreeItem::Ptr ptr = cur.value();
......@@ -362,9 +318,6 @@ void ParserTreeItem::convertTo(QStandardItem *item, bool recursive) const
// locations
add->setData(Utils::locationsToRole(ptr->symbolLocations()),
Constants::SymbolLocationsRole);
if (recursive)
cur.value()->convertTo(add, false);
}
item->appendRow(add);
++cur;
......@@ -380,33 +333,8 @@ bool ParserTreeItem::canFetchMore(QStandardItem *item) const
if (!item)
return false;
// incremental data population - so we have to check children
// count subchildren of both - current QStandardItem and our internal
// for the current UI item
int storedChildren = 0;
for (int i = 0; i < item->rowCount(); i++) {
QStandardItem *child = item->child(i);
if (!child)
continue;
storedChildren += child->rowCount();
}
// children for the internal state
int internalChildren = 0;
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator curHash =
d->symbolInformations.constBegin();
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator endHash =
d->symbolInformations.constEnd();
while (curHash != endHash) {
const ParserTreeItem::Ptr &child = curHash.value();
if (!child.isNull()) {
internalChildren += child->childCount();
// if there is already more items than stored, then can be stopped right now
if (internalChildren > storedChildren)
break;
}
++curHash;
}
int storedChildren = item->rowCount();
int internalChildren = d->symbolInformations.count();
if (storedChildren < internalChildren)
return true;
......@@ -423,25 +351,7 @@ void ParserTreeItem::fetchMore(QStandardItem *item) const
if (!item)
return;
for (int i = 0; i < item->rowCount(); i++) {
QStandardItem *child = item->child(i);
if (!child)
continue;
const SymbolInformation &childInf = Utils::symbolInformationFromItem(child);
if (d->symbolInformations.contains(childInf)) {
const ParserTreeItem::Ptr &childPtr = d->symbolInformations[childInf];
if (childPtr.isNull())
continue;
// create a standard
QScopedPointer<QStandardItem> state(new QStandardItem());
childPtr->convertTo(state.data(), false);
Utils::fetchItemToTarget(child, state.data());
}
}
convertTo(item);
}
/*!
......@@ -450,10 +360,8 @@ void ParserTreeItem::fetchMore(QStandardItem *item) const
void ParserTreeItem::debugDump(int ident) const
{
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator curHash =
d->symbolInformations.constBegin();
QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator endHash =
d->symbolInformations.constEnd();
CitSymbolInformations curHash = d->symbolInformations.constBegin();
CitSymbolInformations endHash = d->symbolInformations.constEnd();
while (curHash != endHash) {
const SymbolInformation &inf = curHash.key();
qDebug() << QString(2*ident, QLatin1Char(' ')) << inf.iconType() << inf.name() << inf.type()
......
......@@ -34,6 +34,7 @@
#include "classviewsymbolinformation.h"
#include <QSharedPointer>
#include <QHash>
QT_FORWARD_DECLARE_CLASS(QStandardItem)
......@@ -74,7 +75,7 @@ public:
int childCount() const;
void convertTo(QStandardItem *item, bool recursive = true) const;
void convertTo(QStandardItem *item) const;
// additional properties
//! Assigned icon
......@@ -85,8 +86,6 @@ public:
void add(const ParserTreeItem::ConstPtr &target);
void subtract(const ParserTreeItem::ConstPtr &target);
bool canFetchMore(QStandardItem *item) const;
void fetchMore(QStandardItem *item) const;
......@@ -97,6 +96,7 @@ protected:
ParserTreeItem &operator=(const ParserTreeItem &other);
private:
typedef QHash<SymbolInformation, ParserTreeItem::Ptr>::const_iterator CitSymbolInformations;
//! Private class data pointer
ParserTreeItemPrivate *d;
};
......
......@@ -80,12 +80,12 @@ bool SymbolInformation::operator<(const SymbolInformation &other) const
return false;
}
int cmp = name().compare(other.name(), Qt::CaseInsensitive);
int cmp = name().compare(other.name());
if (cmp < 0)
return true;
if (cmp > 0)
return false;
return type().compare(other.type(), Qt::CaseInsensitive) < 0;
return type().compare(other.type()) < 0;
}
} // namespace Internal
......
......@@ -124,6 +124,14 @@ void TreeItemModel::fetchMore(const QModelIndex &parent)
return Manager::instance()->fetchMore(itemFromIndex(parent));
}
bool TreeItemModel::hasChildren(const QModelIndex &parent) const
{
if (!parent.isValid())
return true;
return Manager::instance()->hasChildren(itemFromIndex(parent));
}
/*!