(#566) Major rewrite of how to build and update PdmUiTreeOrdering items

Removed findChildItemIndex() which caused performance issues for large
models. Replaced with a new algorithm using std::map. Added unit tests
This commit is contained in:
Magne Sjaastad 2015-10-14 13:23:40 +02:00
parent 59027a1f2e
commit 8698291e7d
9 changed files with 26933 additions and 71 deletions

View File

@ -23,6 +23,8 @@ add_subdirectory(cafTests/cafTestApplication)
add_subdirectory (cafProjectDataModel/cafPdmCore/cafPdmCore_UnitTests)
add_subdirectory (cafProjectDataModel/cafPdmXml/cafPdmXml_UnitTests)
add_subdirectory (cafUserInterface/cafUserInterface_UnitTests)
# Organize sub-projects into folders on Visual Studio
# Turn on using solution folders
set_property(GLOBAL PROPERTY USE_FOLDERS ON)

View File

@ -108,7 +108,7 @@ bool PdmUiTreeOrdering::containsObject(const PdmObjectHandle* object)
assert (object);
for (int cIdx = 0; cIdx < this->childCount(); ++cIdx)
{
PdmUiTreeOrdering* child = dynamic_cast<PdmUiTreeOrdering*>(this->child(cIdx)); // What again ???
PdmUiTreeOrdering* child = this->child(cIdx);
if (child->isRepresentingObject() && child->object() == object)
{

View File

@ -180,8 +180,12 @@ void PdmUiTreeViewModel::updateSubTree(PdmUiItem* pdmRoot)
existingSubTreeRoot->debugDump(0);
#endif
this->layoutAboutToBeChanged();
updateSubTreeRecursive(existingSubTreeRootModIdx, existingSubTreeRoot, newTreeRootTmp);
emit (this->layoutChanged());
delete newTreeRootTmp;
updateEditorsForSubTree(existingSubTreeRoot);
@ -195,99 +199,116 @@ void PdmUiTreeViewModel::updateSubTree(PdmUiItem* pdmRoot)
#endif
}
class RecursiveUpdateData
{
public:
RecursiveUpdateData(QModelIndex mi, PdmUiTreeOrdering* existingChild, PdmUiTreeOrdering* sourceChild)
: m_modelIndex(mi),
m_existingChild(existingChild),
m_sourceChild(sourceChild)
{
};
QModelIndex m_modelIndex;
PdmUiTreeOrdering* m_existingChild;
PdmUiTreeOrdering* m_sourceChild;
};
//--------------------------------------------------------------------------------------------------
/// Makes the destinationSubTreeRoot tree become identical to the tree in sourceSubTreeRoot,
/// Makes the existingSubTreeRoot tree become identical to the tree in sourceSubTreeRoot,
/// calling begin..() end..() to make the UI update accordingly.
/// This assumes that all the items have a pointer an unique PdmObject
//--------------------------------------------------------------------------------------------------
void PdmUiTreeViewModel::updateSubTreeRecursive(const QModelIndex& existingSubTreeRootModIdx, PdmUiTreeOrdering* existingSubTreeRoot, PdmUiTreeOrdering* sourceSubTreeRoot)
{
// First loop over children in the old ui tree, deleting the ones not present in
// the newUiTree
std::vector<RecursiveUpdateData> recursiveUpdateData;
for (int cIdx = 0; cIdx < existingSubTreeRoot->childCount() ; ++cIdx)
// Build map for source items
std::map<caf::PdmUiItem*, int> sourceTreeMap;
for (int i = 0; i < sourceSubTreeRoot->childCount() ; ++i)
{
PdmUiTreeOrdering* oldChild = existingSubTreeRoot->child(cIdx);
PdmUiTreeOrdering* child = sourceSubTreeRoot->child(i);
int childIndex = findChildItemIndex(sourceSubTreeRoot, oldChild->activeItem());
if (childIndex == -1) // Not found
if (child && child->activeItem())
{
this->beginRemoveRows(existingSubTreeRootModIdx, cIdx, cIdx);
existingSubTreeRoot->removeChildren(cIdx, 1);
this->endRemoveRows();
cIdx--;
sourceTreeMap[child->activeItem()] = i;
}
}
// Then loop over the children in the new ui tree, finding the corresponding items in the old tree.
// If they are found, we move them to the correct position.
// If not found, we pull the item out of the old ui tree, inserting it
// into the new tree.
int sourceChildCount = sourceSubTreeRoot->childCount();
int source_cIdx = 0;
for (int cIdx = 0; cIdx < sourceChildCount; ++cIdx, ++source_cIdx)
// Build map for existing items
// Detect items to be deleted from existing tree
std::vector<int> indicesToRemoveFromExisting;
std::map<caf::PdmUiItem*, int> existingTreeMap;
for (int i = 0; i < existingSubTreeRoot->childCount() ; ++i)
{
PdmUiTreeOrdering* newChild = sourceSubTreeRoot->child(source_cIdx);
PdmUiTreeOrdering* child = existingSubTreeRoot->child(i);
int existing_cIdx = findChildItemIndex(existingSubTreeRoot, newChild->activeItem());
if (existing_cIdx == -1) // Not found, move from source to existing
if (child && child->activeItem())
{
this->beginInsertRows(existingSubTreeRootModIdx, cIdx, cIdx);
existingSubTreeRoot->insertChild(cIdx, newChild);
this->endInsertRows();
sourceSubTreeRoot->removeChildrenNoDelete(source_cIdx, 1);
source_cIdx--;
existingTreeMap[child->activeItem()] = i;
}
else if (existing_cIdx != cIdx) // Found, but must be moved
std::map<caf::PdmUiItem*, int>::iterator it = sourceTreeMap.find(child->activeItem());
if (it == sourceTreeMap.end())
{
assert(existing_cIdx > cIdx);
PdmUiTreeOrdering* oldChild = existingSubTreeRoot->child(existing_cIdx);
this->beginMoveRows(existingSubTreeRootModIdx, existing_cIdx, existing_cIdx, existingSubTreeRootModIdx, cIdx);
existingSubTreeRoot->removeChildrenNoDelete(existing_cIdx, 1);
existingSubTreeRoot->insertChild(cIdx, oldChild);
this->endMoveRows();
updateSubTreeRecursive( index(cIdx, 0, existingSubTreeRootModIdx), oldChild, newChild);
}
else // Found the corresponding item in the right place.
{
PdmUiTreeOrdering* oldChild = existingSubTreeRoot->child(existing_cIdx);
updateSubTreeRecursive( index(cIdx, 0, existingSubTreeRootModIdx), oldChild, newChild);
indicesToRemoveFromExisting.push_back(i);
}
}
}
//--------------------------------------------------------------------------------------------------
/// Returns the index of the first child with activeItem() == pdmItemToFindInChildren.
// -1 if not found or pdmItemToFindInChildren == NULL
//--------------------------------------------------------------------------------------------------
int PdmUiTreeViewModel::findChildItemIndex(const PdmUiTreeOrdering * parent, const PdmUiItem* pdmItemToFindInChildren)
{
if (pdmItemToFindInChildren == NULL) return -1;
PdmUiTreeOrdering newOrdering("dummy", "dummy");
for (int i = 0; i < parent->childCount(); ++i)
// Detect items to be moved from source to existing
// Build the correct ordering of items in newOrdering
std::vector<int> indicesToRemoveFromSource;
for (int i = 0; i < sourceSubTreeRoot->childCount() ; ++i)
{
PdmUiTreeOrdering* child = parent->child(i);
if (child->activeItem() == pdmItemToFindInChildren)
{
return i;
}
else if (child->isDisplayItemOnly())
{
// Todo, possibly. Find a way to check for equality based on content
// until done, the display items will always be regenerated with all their children
}
PdmUiTreeOrdering* sourceChild = sourceSubTreeRoot->child(i);
std::map<caf::PdmUiItem*, int>::iterator it = existingTreeMap.find(sourceChild->activeItem());
if (it != existingTreeMap.end())
{
newOrdering.appendChild(existingSubTreeRoot->child(it->second));
recursiveUpdateData.push_back(RecursiveUpdateData(index(newOrdering.childCount() - 1, 0, existingSubTreeRootModIdx), existingSubTreeRoot->child(it->second), sourceChild));
}
else
{
newOrdering.appendChild(sourceChild);
indicesToRemoveFromSource.push_back(i);
}
}
// Delete items with largest index first from existing
for (std::vector<int>::reverse_iterator it = indicesToRemoveFromExisting.rbegin(); it != indicesToRemoveFromExisting.rend(); it++)
{
this->beginRemoveRows(existingSubTreeRootModIdx, *it, *it);
existingSubTreeRoot->removeChildren(*it, 1);
this->endRemoveRows();
}
// Delete items with largest index first from source
for (std::vector<int>::reverse_iterator it = indicesToRemoveFromSource.rbegin(); it != indicesToRemoveFromSource.rend(); it++)
{
// Use the removeChildrenNoDelete() to remove the pointer from the list without deleting the pointer
sourceSubTreeRoot->removeChildrenNoDelete(*it, 1);
}
// Delete all items from existingSubTreeRoot, as the complete list is present in newOrdering
existingSubTreeRoot->removeChildrenNoDelete(0, existingSubTreeRoot->childCount());
// Move all items into existingSubTreeRoot
for (int i = 0; i < newOrdering.childCount(); i++)
{
existingSubTreeRoot->appendChild(newOrdering.child(i));
}
newOrdering.removeChildrenNoDelete(0, newOrdering.childCount());
for (size_t i = 0; i < recursiveUpdateData.size(); i++)
{
updateSubTreeRecursive(recursiveUpdateData[i].m_modelIndex, recursiveUpdateData[i].m_existingChild, recursiveUpdateData[i].m_sourceChild);
}
return -1;
}
//--------------------------------------------------------------------------------------------------

View File

@ -40,7 +40,6 @@
#include <QAbstractItemModel>
#include <QStringList>
#include "cafPdmUiTreeOrdering.h"
namespace caf
{
@ -85,7 +84,6 @@ private:
void resetTree(PdmUiTreeOrdering* root);
void emitDataChanged(const QModelIndex& index);
void updateEditorsForSubTree(PdmUiTreeOrdering* root);
static int findChildItemIndex(const PdmUiTreeOrdering * parent, const PdmUiItem* pdmItemToFindInChildren);
PdmUiTreeOrdering* m_treeOrderingRoot;
QStringList m_columnHeaders;

View File

@ -0,0 +1,61 @@
cmake_minimum_required (VERSION 2.8)
find_package ( Qt4 COMPONENTS QtCore QtGui QtMain )
include (${QT_USE_FILE})
project ( cafUserInterface_UnitTests )
include_directories (
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
${cafProjectDataModel_SOURCE_DIR}
${cafPdmCore_SOURCE_DIR}
${cafPdmUiCore_SOURCE_DIR}
${cafPdmXml_SOURCE_DIR}
${cafUserInterface_SOURCE_DIR}
)
set( PROJECT_FILES
cafUserInterface_UnitTests.cpp
cafPdmUiTreeViewModelTest.cpp
gtest/gtest-all.cpp
)
# add the executable
add_executable (${PROJECT_NAME}
${PROJECT_FILES}
)
source_group("" FILES ${PROJECT_FILES})
message(STATUS ${PROJECT_NAME}" - Qt includes : " ${QT_LIBRARIES})
target_link_libraries ( ${PROJECT_NAME}
cafProjectDataModel
cafPdmUiCore
cafPdmCore
cafPdmXml
cafUserInterface
${QT_LIBRARIES}
)
# Copy Qt Dlls
if (MSVC)
set (QTLIBLIST QtCore QtGui)
foreach (qtlib ${QTLIBLIST})
# Debug
execute_process(COMMAND cmake -E copy_if_different ${QT_BINARY_DIR}/${qtlib}d4.dll ${CMAKE_CURRENT_BINARY_DIR}/Debug/${qtlib}d4.dll)
# Release
execute_process(COMMAND cmake -E copy_if_different ${QT_BINARY_DIR}/${qtlib}4.dll ${CMAKE_CURRENT_BINARY_DIR}/Release/${qtlib}4.dll)
endforeach( qtlib )
endif(MSVC)

View File

@ -0,0 +1,204 @@
#include "gtest/gtest.h"
#include "cafPdmChildArrayField.h"
#include "cafPdmObject.h"
#include "cafPdmUiTreeView.h"
#include <QModelIndex>
#include <QApplication>
using namespace caf;
class SimpleObj: public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
SimpleObj() : PdmObject()
{
CAF_PDM_InitObject("SimpleObj", "", "Tooltip SimpleObj", "WhatsThis SimpleObj");
}
~SimpleObj() {}
};
CAF_PDM_SOURCE_INIT(SimpleObj, "SimpleObj");
class DemoPdmObject: public caf::PdmObject
{
CAF_PDM_HEADER_INIT;
public:
DemoPdmObject()
{
CAF_PDM_InitObject("DemoPdmObject", "", "Tooltip DemoPdmObject", "WhatsThis DemoPdmObject");
CAF_PDM_InitFieldNoDefault(&m_simpleObjPtrField, "SimpleObjPtrField", "SimpleObjPtrField", "", "Tooltip", "WhatsThis");
}
~DemoPdmObject()
{
m_simpleObjPtrField.deleteAllChildObjects();
}
caf::PdmChildArrayField<caf::PdmObjectHandle*> m_simpleObjPtrField;
};
CAF_PDM_SOURCE_INIT(DemoPdmObject, "DemoPdmObject");
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST(PdmUiTreeViewModelTest, DeleteOneItemAndVerifyTreeOrdering)
{
SimpleObj* obj1 = new SimpleObj;
SimpleObj* obj2 = new SimpleObj;
SimpleObj* obj3 = new SimpleObj;
SimpleObj* obj4 = new SimpleObj;
DemoPdmObject* demoObj = new DemoPdmObject;
demoObj->m_simpleObjPtrField.push_back(obj1);
demoObj->m_simpleObjPtrField.push_back(obj2);
demoObj->m_simpleObjPtrField.push_back(obj3);
demoObj->m_simpleObjPtrField.push_back(obj4);
PdmUiTreeView treeView;
treeView.setPdmItem(demoObj);
QModelIndex mi;
mi = treeView.findModelIndex(obj1);
EXPECT_TRUE(mi.isValid());
demoObj->m_simpleObjPtrField.removeChildObject(obj1);
demoObj->m_simpleObjPtrField().uiCapability()->updateConnectedEditors();
mi = treeView.findModelIndex(obj1);
EXPECT_FALSE(mi.isValid());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST(PdmUiTreeViewModelTest, AddOneItemAndVerifyTreeOrdering)
{
SimpleObj* obj1 = new SimpleObj;
SimpleObj* obj2 = new SimpleObj;
SimpleObj* obj3 = new SimpleObj;
SimpleObj* obj4 = new SimpleObj;
DemoPdmObject* demoObj = new DemoPdmObject;
demoObj->m_simpleObjPtrField.push_back(obj1);
demoObj->m_simpleObjPtrField.push_back(obj2);
demoObj->m_simpleObjPtrField.push_back(obj3);
PdmUiTreeView treeView;
treeView.setPdmItem(demoObj);
QModelIndex mi;
mi = treeView.findModelIndex(obj4);
EXPECT_FALSE(mi.isValid());
demoObj->m_simpleObjPtrField.push_back(obj4);
demoObj->m_simpleObjPtrField().uiCapability()->updateConnectedEditors();
mi = treeView.findModelIndex(obj4);
EXPECT_TRUE(mi.isValid());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST(PdmUiTreeViewModelTest, ChangeOrderingAndVerifyTreeOrdering)
{
SimpleObj* obj1 = new SimpleObj;
SimpleObj* obj2 = new SimpleObj;
SimpleObj* obj3 = new SimpleObj;
SimpleObj* obj4 = new SimpleObj;
DemoPdmObject* demoObj = new DemoPdmObject;
demoObj->m_simpleObjPtrField.push_back(obj1);
demoObj->m_simpleObjPtrField.push_back(obj2);
demoObj->m_simpleObjPtrField.push_back(obj3);
demoObj->m_simpleObjPtrField.push_back(obj4);
PdmUiTreeView treeView;
treeView.setPdmItem(demoObj);
QModelIndex mi;
mi = treeView.findModelIndex(obj4);
EXPECT_EQ(3, mi.row());
demoObj->m_simpleObjPtrField.clear();
demoObj->m_simpleObjPtrField.push_back(obj1);
demoObj->m_simpleObjPtrField.push_back(obj4);
demoObj->m_simpleObjPtrField.push_back(obj3);
demoObj->m_simpleObjPtrField.push_back(obj2);
demoObj->m_simpleObjPtrField().uiCapability()->updateConnectedEditors();
mi = treeView.findModelIndex(obj4);
EXPECT_EQ(1, mi.row());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST(PdmUiTreeViewModelTest, ChangeDeepInTreeNotifyRootAndVerifyTreeOrdering)
{
DemoPdmObject* root = new DemoPdmObject;
SimpleObj* rootObj1 = new SimpleObj;
root->m_simpleObjPtrField.push_back(rootObj1);
DemoPdmObject* demoObj = new DemoPdmObject;
root->m_simpleObjPtrField.push_back(demoObj);
SimpleObj* obj1 = new SimpleObj;
SimpleObj* obj2 = new SimpleObj;
SimpleObj* obj3 = new SimpleObj;
SimpleObj* obj4 = new SimpleObj;
demoObj->m_simpleObjPtrField.push_back(obj1);
demoObj->m_simpleObjPtrField.push_back(obj2);
demoObj->m_simpleObjPtrField.push_back(obj3);
demoObj->m_simpleObjPtrField.push_back(obj4);
PdmUiTreeView treeView;
treeView.setPdmItem(root);
QModelIndex mi;
mi = treeView.findModelIndex(obj4);
EXPECT_EQ(3, mi.row());
demoObj->m_simpleObjPtrField.removeChildObject(obj4);
root->m_simpleObjPtrField().uiCapability()->updateConnectedEditors();
mi = treeView.findModelIndex(obj4);
EXPECT_FALSE(mi.isValid());
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
TEST(PdmUiTreeViewModelTest, DISABLED_PerformanceLargeNumberOfItems)
{
//int objCount = 20000;
int objCount = 100000;
DemoPdmObject* demoObj = new DemoPdmObject;
for (int i = 0; i < objCount; i++)
{
demoObj->m_simpleObjPtrField.push_back(new SimpleObj);
}
PdmUiTreeView treeView;
treeView.setPdmItem(demoObj);
demoObj->m_simpleObjPtrField().uiCapability()->updateConnectedEditors();
}

View File

@ -0,0 +1,59 @@
//##################################################################################################
//
// Custom Visualization Core library
// Copyright (C) 2014 Ceetron Solutions AS
//
// This library may be used under the terms of either the GNU General Public License or
// the GNU Lesser General Public License as follows:
//
// GNU General Public License Usage
// This library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <<http://www.gnu.org/licenses/gpl.html>>
// for more details.
//
// GNU Lesser General Public License Usage
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
// See the GNU Lesser General Public License at <<http://www.gnu.org/licenses/lgpl-2.1.html>>
// for more details.
//
//##################################################################################################
#include "gtest/gtest.h"
#include <stdio.h>
#include <iostream>
#include <string>
#include <QApplication>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int main(int argc, char **argv)
{
QApplication app(argc, argv);
testing::InitGoogleTest(&argc, argv);
int result = RUN_ALL_TESTS();
char text[5];
std::cin.getline(text, 5);
return result;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff