From b19647862b789626f75fef7a1aa49c3ffc3a0940 Mon Sep 17 00:00:00 2001 From: "Zheng, Lei" Date: Thu, 4 Jul 2019 19:14:30 +0800 Subject: [PATCH] DocumentObject: API implementation change of In/OutList Added getInListEx() as a more efficient algorithm for recursive in list. Replacing the original getInListRecursive(). Added out list object and name cache to improve default implementation of getSubObject(). --- src/App/DocumentObject.cpp | 205 ++++++++++++++++++++++++++----------- src/App/DocumentObject.h | 39 ++++++- 2 files changed, 182 insertions(+), 62 deletions(-) diff --git a/src/App/DocumentObject.cpp b/src/App/DocumentObject.cpp index e81260e7d6af..4fb51929fd0c 100644 --- a/src/App/DocumentObject.cpp +++ b/src/App/DocumentObject.cpp @@ -239,40 +239,38 @@ const char* DocumentObject::detachFromDocument() return name ? name->c_str() : 0; } -std::vector DocumentObject::getOutList(void) const -{ - std::vector List; - std::vector ret; - getPropertyList(List); - for (std::vector::const_iterator It = List.begin();It != List.end(); ++It) { - if ((*It)->isDerivedFrom(PropertyLinkList::getClassTypeId())) { - const std::vector &OutList = static_cast(*It)->getValues(); - for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { - if (*It2) - ret.push_back(*It2); - } - } - else if ((*It)->isDerivedFrom(PropertyLinkSubList::getClassTypeId())) { - const std::vector &OutList = static_cast(*It)->getValues(); - for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { - if (*It2) - ret.push_back(*It2); - } - } - else if ((*It)->isDerivedFrom(PropertyLink::getClassTypeId())) { - if (static_cast(*It)->getValue()) - ret.push_back(static_cast(*It)->getValue()); - } - else if ((*It)->isDerivedFrom(PropertyLinkSub::getClassTypeId())) { - if (static_cast(*It)->getValue()) - ret.push_back(static_cast(*It)->getValue()); - } +const std::vector &DocumentObject::getOutList() const { + if(!_outListCached) { + _outList.clear(); + getOutList(0,_outList); + _outListCached = true; } + return _outList; +} - // Get document objects that this document object relies on - ExpressionEngine.getDocumentObjectDeps(ret); +std::vector DocumentObject::getOutList(int options) const +{ + std::vector res; + getOutList(options,res); + return res; +} - return ret; +void DocumentObject::getOutList(int options, std::vector &res) const { + if(_outListCached && !options) { + res.insert(res.end(),_outList.begin(),_outList.end()); + return; + } + std::vector props; + getPropertyList(props); + bool noHidden = !!(options & OutListNoHidden); + bool noXLinked = !!(options & OutListNoXLinked); + for(auto prop : props) { + auto link = dynamic_cast(prop); + if(link && (!noXLinked || !PropertyXLink::supportXLink(prop))) + link->getLinks(res,noHidden); + } + if(!(options & OutListNoExpression)) + ExpressionEngine.getLinks(res); } std::vector DocumentObject::getOutListOfProperty(App::Property* prop) const @@ -281,33 +279,9 @@ std::vector DocumentObject::getOutListOfProperty(App::Prop if (!prop || prop->getContainer() != this) return ret; - if (prop->isDerivedFrom(PropertyLinkList::getClassTypeId())) { - const std::vector &OutList = static_cast(prop)->getValues(); - for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { - if (*It2) - ret.push_back(*It2); - } - } - else if (prop->isDerivedFrom(PropertyLinkSubList::getClassTypeId())) { - const std::vector &OutList = static_cast(prop)->getValues(); - for (std::vector::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) { - if (*It2) - ret.push_back(*It2); - } - } - else if (prop->isDerivedFrom(PropertyLink::getClassTypeId())) { - if (static_cast(prop)->getValue()) - ret.push_back(static_cast(prop)->getValue()); - } - else if (prop->isDerivedFrom(PropertyLinkSub::getClassTypeId())) { - if (static_cast(prop)->getValue()) - ret.push_back(static_cast(prop)->getValue()); - } - else if (prop == &ExpressionEngine) { - // Get document objects that this document object relies on - ExpressionEngine.getDocumentObjectDeps(ret); - } - + auto link = dynamic_cast(prop); + if(link) + link->getLinks(ret); return ret; } @@ -322,7 +296,7 @@ std::vector DocumentObject::getInList(void) const #else // ifndef USE_OLD_DAG -std::vector DocumentObject::getInList(void) const +const std::vector &DocumentObject::getInList(void) const { return _inList; } @@ -330,6 +304,8 @@ std::vector DocumentObject::getInList(void) const #endif // if USE_OLD_DAG +#if 0 + void _getInListRecursive(std::set& objSet, const DocumentObject* obj, const DocumentObject* checkObj, int depth) @@ -352,7 +328,8 @@ std::vector DocumentObject::getInListRecursive(void) const // number of objects in document is a good estimate in result size // int maxDepth = getDocument()->countObjects() +2; int maxDepth = GetApplication().checkLinkDepth(0); - std::set result; + std::vector result; + result.reserve(maxDepth); // using a rcursie helper to collect all InLists _getInListRecursive(result, this, this, maxDepth); @@ -362,6 +339,95 @@ std::vector DocumentObject::getInListRecursive(void) const return array; } +#else +// The original algorithm is highly inefficient in some special case. +// Considering an object is linked by every other objects. After exculding this +// object, there is another object linked by every other of the remaining +// objects, and so on. The vector 'result' above will be of magnitude n^2. +// Even if we replace the vector with a set, we still need to visit that amount +// of objects. And this may not be the worst case. getInListEx() has no such +// problem. + +std::vector DocumentObject::getInListRecursive(void) const { + std::set inSet; + std::vector res; + getInListEx(inSet,true,&res); + return res; +} + +#endif + +// More efficient algorithm to find the recursive inList of an object, +// including possible external parents. One shortcoming of this algorithm is +// it does not detect cyclic reference, althgouth it won't crash either. +void DocumentObject::getInListEx(std::set &inSet, + bool recursive, std::vector *inList) const +{ +#ifdef USE_OLD_DAG + std::map > outLists; + + // Old DAG does not have pre-built InList, and must calculate The InList by + // going through all objects' OutLists. So we collect all objects and their + // outLists first here. + for(auto doc : GetApplication().getDocuments()) { + for(auto obj : doc->getObjects()) { + if(!obj || !obj->getNameInDocument() || obj==this) + continue; + const auto &outList = obj->getOutList(); + outLists[obj].insert(outList.begin(),outList.end()); + } + } + + std::stack pendings; + pendings.push(const_cast(this)); + while(pendings.size()) { + auto obj = pendings.top(); + pendings.pop(); + for(auto &v : outLists) { + if(v.first == obj) continue; + auto &outList = v.second; + // Check the outList to see if the object is there, and pend the + // object for recrusive check if it's not already in the inList + if(outList.find(obj)!=outList.end() && + inSet.insert(v.first).second && + recursive) + { + pendings.push(v.first); + } + } + } +#else // USE_OLD_DAG + + if(!recursive) { + inSet.insert(_inList.begin(),_inList.end()); + if(inList) + *inList = _inList; + return; + } + + std::stack pendings; + pendings.push(const_cast(this)); + while(pendings.size()) { + auto obj = pendings.top(); + pendings.pop(); + for(auto o : obj->getInList()) { + if(o && o->getNameInDocument() && inSet.insert(o).second) { + pendings.push(o); + if(inList) + inList->push_back(o); + } + } + } + +#endif +} + +std::set DocumentObject::getInListEx(bool recursive) const { + std::set ret; + getInListEx(ret,recursive); + return ret; +} + void _getOutListRecursive(std::set& objSet, const DocumentObject* obj, const DocumentObject* checkObj, int depth) @@ -420,8 +486,12 @@ bool _isInInListRecursive(const DocumentObject* act, bool DocumentObject::isInInListRecursive(DocumentObject *linkTo) const { +#if 0 int maxDepth = getDocument()->countObjects() + 2; return _isInInListRecursive(this, linkTo, maxDepth); +#else + return this==linkTo || getInListEx(true).count(linkTo); +#endif } bool DocumentObject::isInInList(DocumentObject *linkTo) const @@ -488,6 +558,7 @@ bool DocumentObject::testIfLinkDAGCompatible(DocumentObject *linkTo) const bool DocumentObject::testIfLinkDAGCompatible(const std::vector &linksTo) const { +#if 0 Document* doc = this->getDocument(); if (!doc) throw Base::RuntimeError("DocumentObject::testIfLinkIsDAG: object is not in any document."); @@ -497,6 +568,14 @@ bool DocumentObject::testIfLinkDAGCompatible(const std::vector return false; else return true; +#else + auto inLists = getInListEx(true); + inLists.emplace(const_cast(this)); + for(auto obj : linksTo) + if(inLists.count(obj)) + return false; + return true; +#endif } bool DocumentObject::testIfLinkDAGCompatible(PropertyLinkSubList &linksTo) const @@ -620,6 +699,12 @@ void DocumentObject::onChanged(const Property* prop) signalChanged(*this,*prop); } +void DocumentObject::clearOutListCache() const { + _outList.clear(); + _outListMap.clear(); + _outListCached = false; +} + PyObject *DocumentObject::getPyObject(void) { if (PythonObject.is(Py::_None())) { diff --git a/src/App/DocumentObject.h b/src/App/DocumentObject.h index 88a2d63d110b..ea2895a986f0 100644 --- a/src/App/DocumentObject.h +++ b/src/App/DocumentObject.h @@ -185,18 +185,50 @@ class AppExport DocumentObject: public App::TransactionalObject a DAG (directed acyclic graph). */ //@{ + /// OutList options + enum OutListOption { + /// Do not include link from expression engine + OutListNoExpression = 1, + /// Do not hide any link (i.e. include links with LinkScopeHidden) + OutListNoHidden = 2, + /// Do not include link from PropertyXLink + OutListNoXLinked = 4, + }; /// returns a list of objects this object is pointing to by Links - std::vector getOutList(void) const; + const std::vector &getOutList() const; + std::vector getOutList(int option) const; + void getOutList(int option, std::vector &res) const; + /// returns a list of objects linked by the property std::vector getOutListOfProperty(App::Property*) const; /// returns a list of objects this object is pointing to by Links and all further descended std::vector getOutListRecursive(void) const; + /// clear internal out list cache + void clearOutListCache() const; /// get all possible paths from this to another object following the OutList std::vector > getPathsByOutList(App::DocumentObject* to) const; +#ifdef USE_OLD_DAG /// get all objects link to this object - std::vector getInList(void) const; + std::vector getInList(void) const +#else + const std::vector &getInList(void) const; +#endif /// get all objects link directly or indirectly to this object std::vector getInListRecursive(void) const; + /** Get a set of all objects linking to this object, including possible external parent objects + * + * @param inSet [out]: a set containing all objects linking to this object. + * @param recursive [in]: whether to obtain recursive in list + * @param inList [in, out]: optional pointer to a vector holding the output + * objects, with the furthest linking object ordered last. + */ + void getInListEx(std::set &inSet, + bool recursive, std::vector *inList=0) const; + /** Return a set of all objects linking to this object, including possible external parent objects + * @param recursive [in]: whether to obtain recursive in list + */ + std::set getInListEx(bool recursive) const; + /// get group if object is part of a group, otherwise 0 is returned DocumentObjectGroup* getGroup() const; @@ -557,6 +589,9 @@ class AppExport DocumentObject: public App::TransactionalObject // Back pointer to all the fathers in a DAG of the document // this is used by the document (via friend) to have a effective DAG handling std::vector _inList; + mutable std::vector _outList; + mutable std::unordered_map _outListMap; + mutable bool _outListCached = false; }; } //namespace App