App: Invoke signalBeforeRecompute() on the GUI thread
Historically, `App::Document::recompute()` ran entirely on the **main**
(GUI) thread and directly emitted `signalBeforeRecompute()`.
* Add-ons like **Assembly3** and others depend on that signal for
setup/teardown hooks before any recompute work begins.
* After offloading `recompute()` into a background worker thread to keep
the UI responsive, calling `signalBeforeRecompute()` directly from the
worker would break thread-affinity rules and silently break
compatibility with those add-ons.
**Solution**
1. **Introduce a generic hook** (`PreRecomputeHook`) in
**App::Document**:
* A `std::function<void()>` that, if set, is invoked at the very
start of `recompute()`.
* Core code stays Qt-free—only knows to call a callback if one
exists.
2. **Wire up the hook in `Gui::Document`**:
* In the GUI wrapper’s constructor, install a hook that calls
`callSignalBeforeRecompute()`.
* `callSignalBeforeRecompute()` uses `QMetaObject::invokeMethod(...,
Qt::BlockingQueuedConnection)` to enqueue `signalBeforeRecompute()` on
the GUI thread and **block** the worker until it completes.
* If already on the GUI thread, it simply calls the signal directly.
3. **Maintain add-on compatibility**:
* From the add-on’s perspective nothing changes—they still receive
`signalBeforeRecompute()` on the main thread before any recompute work.
* Internally, the recompute body now runs on a worker thread,
improving UI responsiveness without breaking existing hooks.
**Result**
* **Recompute** remains fully backward-compatible for add-ons like
Assembly3.
* **UI thread** still handles all GUI-related signaling.
* **Worker thread** performs the actual heavy lifting, unblocked only
once the GUI is primed and all pre-recompute signals have been
delivered.
This commit is contained in:
committed by
Benjamin Nauck
parent
6cd828d986
commit
1f5e0f6494
@@ -2878,6 +2878,11 @@ void Document::renameObjectIdentifiers(
|
||||
}
|
||||
}
|
||||
|
||||
void Document::setPreRecomputeHook(const PreRecomputeHook& hook)
|
||||
{
|
||||
d->_preRecomputeHook = hook;
|
||||
}
|
||||
|
||||
int Document::recompute(const std::vector<DocumentObject*>& objs,
|
||||
bool force,
|
||||
bool* hasError,
|
||||
@@ -2918,7 +2923,13 @@ int Document::recompute(const std::vector<DocumentObject*>& objs,
|
||||
FC_TIME_INIT(t);
|
||||
|
||||
Base::ObjectStatusLocker<Document::Status, Document> exe(Document::Recomputing, this);
|
||||
signalBeforeRecompute(*this);
|
||||
|
||||
// This will hop into the main thread, fire signalBeforeRecompute(),
|
||||
// and *block* the worker until the main thread is done, avoiding races
|
||||
// between any running Python code and the rest of the recompute call.
|
||||
if (d->_preRecomputeHook) {
|
||||
d->_preRecomputeHook();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// FIXME Comment by Realthunder:
|
||||
|
||||
@@ -193,6 +193,8 @@ public:
|
||||
//@}
|
||||
// NOLINTEND
|
||||
|
||||
using PreRecomputeHook = std::function<void()>;
|
||||
void setPreRecomputeHook(const PreRecomputeHook& hook);
|
||||
|
||||
void clearDocument();
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ struct DocumentP
|
||||
|
||||
StringHasherRef Hasher {new StringHasher};
|
||||
|
||||
Document::PreRecomputeHook _preRecomputeHook;
|
||||
|
||||
DocumentP();
|
||||
|
||||
void addRecomputeLog(const char* why, App::DocumentObject* obj)
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
# include <QOpenGLWidget>
|
||||
# include <QTextStream>
|
||||
# include <QTimer>
|
||||
# include <QThread>
|
||||
# include <QStatusBar>
|
||||
# include <Inventor/actions/SoSearchAction.h>
|
||||
# include <Inventor/nodes/SoSeparator.h>
|
||||
@@ -505,6 +506,8 @@ Document::Document(App::Document* pcDocument,Application * app)
|
||||
(std::bind(&Gui::Document::slotTransactionRemove, this, sp::_1, sp::_2));
|
||||
//NOLINTEND
|
||||
|
||||
pcDocument->setPreRecomputeHook([this] { callSignalBeforeRecompute(); });
|
||||
|
||||
// pointer to the python class
|
||||
// NOTE: As this Python object doesn't get returned to the interpreter we
|
||||
// mustn't increment it (Werner Jan-12-2006)
|
||||
@@ -1193,6 +1196,25 @@ void Document::slotTouchedObject(const App::DocumentObject &Obj)
|
||||
}
|
||||
}
|
||||
|
||||
// helper that guarantees signalBeforeRecompute call is executed in the GUI thread and
|
||||
// that the worker waits until it finishes
|
||||
void Document::callSignalBeforeRecompute()
|
||||
{
|
||||
auto invokeSignalBeforeRecompute = [this]{
|
||||
// this runs in the GUI thread
|
||||
this->getDocument()->signalBeforeRecompute(*this->getDocument());
|
||||
};
|
||||
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
// already on GUI thread – no hop, just call it
|
||||
invokeSignalBeforeRecompute();
|
||||
} else {
|
||||
// hop to GUI and *block* until it returns
|
||||
QMetaObject::invokeMethod(qApp, std::move(invokeSignalBeforeRecompute),
|
||||
Qt::BlockingQueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void Document::addViewProvider(Gui::ViewProviderDocumentObject* vp)
|
||||
{
|
||||
// Hint: The undo/redo first adds the view provider to the Gui
|
||||
|
||||
@@ -94,6 +94,7 @@ protected:
|
||||
void slotSkipRecompute(const App::Document &doc, const std::vector<App::DocumentObject*> &objs);
|
||||
void slotTouchedObject(const App::DocumentObject &);
|
||||
void slotChangePropertyEditor(const App::Document&, const App::Property &);
|
||||
void callSignalBeforeRecompute();
|
||||
//@}
|
||||
|
||||
public:
|
||||
|
||||
Reference in New Issue
Block a user