Skip to content

Commit

Permalink
Document: 2-pass recompute for dependency inversion
Browse files Browse the repository at this point in the history
Here is a simple example of dependency inversion:

A parent object has some child object, but the child object can only be
updated after the parent object finished update, because it requires
some information from the parent.

A concrete example will be Sketcher::SkechExport, that export partial
geometry element from a SkechObject. For better object hierarchy,
SketchExport is grouped under SketchObject. However, it can only be
updated after SketchObject finished recompute

This patch offers a quick and dirty solution to this dependency
inversion problem. When Document::recompute(objs) is called, the first
pass proceed as normal. After its done, it will check if any object is
still touched, and mark those touched object with
ObjectStatus::Recompute2 flag. It will then launch a second pass of
recompute. Those parent objects that need dependency inversion shall
check its children for this flag, and do not touch them if found. If
there are still objects touched after second pass, an error message
will be printed.
  • Loading branch information
realthunder committed Feb 24, 2018
1 parent cd83555 commit 70c3d85
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 25 deletions.
62 changes: 38 additions & 24 deletions src/App/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2491,15 +2491,13 @@ int Document::recompute(const std::vector<App::DocumentObject*> &objs)

#else //ifdef USE_OLD_DAG

int Document::recompute(const std::vector<App::DocumentObject*> &objs)
{
int Document::recompute(const std::vector<App::DocumentObject*> &objs) {
int objectCount = 0;

if (testStatus(Document::Recomputing)) {
// this is clearly a bug in the calling instance
throw Base::RuntimeError("Nested recomputes of a document are not allowed");
}

int objectCount = 0;

// The 'SkipRecompute' flag can be (tmp.) set to avoid too many
// time expensive recomputes
bool skip = testStatus(Document::SkipRecompute);
Expand Down Expand Up @@ -2534,7 +2532,7 @@ int Document::recompute(const std::vector<App::DocumentObject*> &objs)
cerr << "App::Document::recompute(): cyclic dependency detected" << endl;
topoSortedObjects = d->partialTopologicalSort(depObjs);
}
for (auto objIt = topoSortedObjects.rbegin(); objIt != topoSortedObjects.rend(); ++objIt){
std::reverse(topoSortedObjects.begin(),topoSortedObjects.end());
#else
vector<DocumentObject*> topoSortedObjects;
try {
Expand All @@ -2544,28 +2542,44 @@ int Document::recompute(const std::vector<App::DocumentObject*> &objs)
topoSortedObjects = d->partialTopologicalSort(getDependencyList(objs.empty()?d->objectArray:objs,false));
std::reverse(topoSortedObjects.begin(),topoSortedObjects.end());
}
for (auto objIt = topoSortedObjects.begin(); objIt != topoSortedObjects.end(); ++objIt){
#endif
// ask the object if it should be recomputed
if ((*objIt)->isTouched() || (*objIt)->mustExecute() == 1){
objectCount++;
if (_recomputeFeature(*objIt)) {
// if something happened break execution of recompute
return -1;
}
else{
(*objIt)->purgeTouched();
// set all dependent object touched to force recompute
for (auto inObjIt : (*objIt)->getInList())
inObjIt->touch();
size_t idx = 0;
// maximum two passes to allow some form of dependency inversion
for(int passes=0; passes<2 && idx<topoSortedObjects.size(); ++passes) {
FC_LOG("Recompute pass " << passes);
for (;idx<topoSortedObjects.size();++idx) {
auto obj = topoSortedObjects[idx];
// ask the object if it should be recomputed
if (obj->isTouched() || obj->mustExecute() == 1) {
objectCount++;
if (_recomputeFeature(obj)) {
// if something happened break execution of recompute
return -1;
}
else{
obj->purgeTouched();
// set all dependent object touched to force recompute
for (auto inObjIt : obj->getInList())
inObjIt->touch();
}
}
}
}
if(FC_LOG_INSTANCE.isEnabled(FC_LOGLEVEL_LOG)) {
// check if all objects are recalculated which were thouched
for (auto obj : topoSortedObjects) {
if (obj->isTouched())
FC_ERR(obj->getNameInDocument() << " still touched after recompute");
for (size_t i=0;i<topoSortedObjects.size();++i) {
auto obj = topoSortedObjects[i];
obj->setStatus(ObjectStatus::Recompute2,false);
if(obj->isTouched()) {
if(passes>0)
FC_ERR(obj->getNameInDocument() << " still touched after recompute");
else{
FC_LOG(obj->getNameInDocument() << " still touched after recompute");
if(idx>=topoSortedObjects.size()) {
// let's start the next pass on the first touched object
idx = i;
}
obj->setStatus(ObjectStatus::Recompute2,true);
}
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/App/DocumentObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ enum ObjectStatus {
Remove = 5,
PythonCall = 6,
Destroy = 7,
Expand = 16
Recompute2 = 8,
Expand = 16,
};

/** Return object for feature execution
Expand Down

0 comments on commit 70c3d85

Please sign in to comment.