Skip to content

Commit

Permalink
DocumentObject: API implementation change of In/OutList
Browse files Browse the repository at this point in the history
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().
  • Loading branch information
realthunder committed Jul 16, 2019
1 parent 429c484 commit 380947e
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 62 deletions.
205 changes: 145 additions & 60 deletions src/App/DocumentObject.cpp
Expand Up @@ -235,40 +235,38 @@ const char* DocumentObject::detachFromDocument()
return name ? name->c_str() : 0;
}

std::vector<DocumentObject*> DocumentObject::getOutList(void) const
{
std::vector<Property*> List;
std::vector<DocumentObject*> ret;
getPropertyList(List);
for (std::vector<Property*>::const_iterator It = List.begin();It != List.end(); ++It) {
if ((*It)->isDerivedFrom(PropertyLinkList::getClassTypeId())) {
const std::vector<DocumentObject*> &OutList = static_cast<PropertyLinkList*>(*It)->getValues();
for (std::vector<DocumentObject*>::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) {
if (*It2)
ret.push_back(*It2);
}
}
else if ((*It)->isDerivedFrom(PropertyLinkSubList::getClassTypeId())) {
const std::vector<DocumentObject*> &OutList = static_cast<PropertyLinkSubList*>(*It)->getValues();
for (std::vector<DocumentObject*>::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) {
if (*It2)
ret.push_back(*It2);
}
}
else if ((*It)->isDerivedFrom(PropertyLink::getClassTypeId())) {
if (static_cast<PropertyLink*>(*It)->getValue())
ret.push_back(static_cast<PropertyLink*>(*It)->getValue());
}
else if ((*It)->isDerivedFrom(PropertyLinkSub::getClassTypeId())) {
if (static_cast<PropertyLinkSub*>(*It)->getValue())
ret.push_back(static_cast<PropertyLinkSub*>(*It)->getValue());
}
const std::vector<DocumentObject*> &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*> DocumentObject::getOutList(int options) const
{
std::vector<DocumentObject*> res;
getOutList(options,res);
return res;
}

return ret;
void DocumentObject::getOutList(int options, std::vector<DocumentObject*> &res) const {
if(_outListCached && !options) {
res.insert(res.end(),_outList.begin(),_outList.end());
return;
}
std::vector<Property*> props;
getPropertyList(props);
bool noHidden = !!(options & OutListNoHidden);
bool noXLinked = !!(options & OutListNoXLinked);
for(auto prop : props) {
auto link = dynamic_cast<PropertyLinkBase*>(prop);
if(link && (!noXLinked || !PropertyXLink::supportXLink(prop)))
link->getLinks(res,noHidden);
}
if(!(options & OutListNoExpression))
ExpressionEngine.getLinks(res);
}

std::vector<App::DocumentObject*> DocumentObject::getOutListOfProperty(App::Property* prop) const
Expand All @@ -277,33 +275,9 @@ std::vector<App::DocumentObject*> DocumentObject::getOutListOfProperty(App::Prop
if (!prop || prop->getContainer() != this)
return ret;

if (prop->isDerivedFrom(PropertyLinkList::getClassTypeId())) {
const std::vector<DocumentObject*> &OutList = static_cast<PropertyLinkList*>(prop)->getValues();
for (std::vector<DocumentObject*>::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) {
if (*It2)
ret.push_back(*It2);
}
}
else if (prop->isDerivedFrom(PropertyLinkSubList::getClassTypeId())) {
const std::vector<DocumentObject*> &OutList = static_cast<PropertyLinkSubList*>(prop)->getValues();
for (std::vector<DocumentObject*>::const_iterator It2 = OutList.begin();It2 != OutList.end(); ++It2) {
if (*It2)
ret.push_back(*It2);
}
}
else if (prop->isDerivedFrom(PropertyLink::getClassTypeId())) {
if (static_cast<PropertyLink*>(prop)->getValue())
ret.push_back(static_cast<PropertyLink*>(prop)->getValue());
}
else if (prop->isDerivedFrom(PropertyLinkSub::getClassTypeId())) {
if (static_cast<PropertyLinkSub*>(prop)->getValue())
ret.push_back(static_cast<PropertyLinkSub*>(prop)->getValue());
}
else if (prop == &ExpressionEngine) {
// Get document objects that this document object relies on
ExpressionEngine.getDocumentObjectDeps(ret);
}

auto link = dynamic_cast<PropertyLinkBase*>(prop);
if(link)
link->getLinks(ret);
return ret;
}

Expand All @@ -318,14 +292,16 @@ std::vector<App::DocumentObject*> DocumentObject::getInList(void) const

#else // ifndef USE_OLD_DAG

std::vector<App::DocumentObject*> DocumentObject::getInList(void) const
const std::vector<App::DocumentObject*> &DocumentObject::getInList(void) const
{
return _inList;
}

#endif // if USE_OLD_DAG


#if 0

void _getInListRecursive(std::set<DocumentObject*>& objSet,
const DocumentObject* obj,
const DocumentObject* checkObj, int depth)
Expand All @@ -348,7 +324,8 @@ std::vector<App::DocumentObject*> 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<App::DocumentObject*> result;
std::vector<App::DocumentObject*> result;
result.reserve(maxDepth);

// using a rcursie helper to collect all InLists
_getInListRecursive(result, this, this, maxDepth);
Expand All @@ -358,6 +335,95 @@ std::vector<App::DocumentObject*> 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<App::DocumentObject*> DocumentObject::getInListRecursive(void) const {
std::set<App::DocumentObject*> inSet;
std::vector<App::DocumentObject*> 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<App::DocumentObject*> &inSet,
bool recursive, std::vector<App::DocumentObject*> *inList) const
{
#ifdef USE_OLD_DAG
std::map<DocumentObject*,std::set<App::DocumentObject*> > 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<DocumentObject*> pendings;
pendings.push(const_cast<DocumentObject*>(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<DocumentObject*> pendings;
pendings.push(const_cast<DocumentObject*>(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<App::DocumentObject*> DocumentObject::getInListEx(bool recursive) const {
std::set<App::DocumentObject*> ret;
getInListEx(ret,recursive);
return ret;
}

void _getOutListRecursive(std::set<DocumentObject*>& objSet,
const DocumentObject* obj,
const DocumentObject* checkObj, int depth)
Expand Down Expand Up @@ -416,8 +482,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
Expand Down Expand Up @@ -484,6 +554,7 @@ bool DocumentObject::testIfLinkDAGCompatible(DocumentObject *linkTo) const

bool DocumentObject::testIfLinkDAGCompatible(const std::vector<DocumentObject *> &linksTo) const
{
#if 0
Document* doc = this->getDocument();
if (!doc)
throw Base::RuntimeError("DocumentObject::testIfLinkIsDAG: object is not in any document.");
Expand All @@ -493,6 +564,14 @@ bool DocumentObject::testIfLinkDAGCompatible(const std::vector<DocumentObject *>
return false;
else
return true;
#else
auto inLists = getInListEx(true);
inLists.emplace(const_cast<DocumentObject*>(this));
for(auto obj : linksTo)
if(inLists.count(obj))
return false;
return true;
#endif
}

bool DocumentObject::testIfLinkDAGCompatible(PropertyLinkSubList &linksTo) const
Expand Down Expand Up @@ -616,6 +695,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())) {
Expand Down
39 changes: 37 additions & 2 deletions src/App/DocumentObject.h
Expand Up @@ -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<App::DocumentObject*> getOutList(void) const;
const std::vector<App::DocumentObject*> &getOutList() const;
std::vector<App::DocumentObject*> getOutList(int option) const;
void getOutList(int option, std::vector<App::DocumentObject*> &res) const;

/// returns a list of objects linked by the property
std::vector<App::DocumentObject*> getOutListOfProperty(App::Property*) const;
/// returns a list of objects this object is pointing to by Links and all further descended
std::vector<App::DocumentObject*> 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<std::list<App::DocumentObject*> > getPathsByOutList(App::DocumentObject* to) const;
#ifdef USE_OLD_DAG
/// get all objects link to this object
std::vector<App::DocumentObject*> getInList(void) const;
std::vector<App::DocumentObject*> getInList(void) const
#else
const std::vector<App::DocumentObject*> &getInList(void) const;
#endif
/// get all objects link directly or indirectly to this object
std::vector<App::DocumentObject*> 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<App::DocumentObject*> &inSet,
bool recursive, std::vector<App::DocumentObject*> *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<App::DocumentObject*> getInListEx(bool recursive) const;

/// get group if object is part of a group, otherwise 0 is returned
DocumentObjectGroup* getGroup() const;

Expand Down Expand Up @@ -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<App::DocumentObject*> _inList;
mutable std::vector<App::DocumentObject *> _outList;
mutable std::unordered_map<const char *, App::DocumentObject*, CStringHasher, CStringHasher> _outListMap;
mutable bool _outListCached = false;
};

} //namespace App
Expand Down

0 comments on commit 380947e

Please sign in to comment.