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:
Joao Matos
2025-05-18 13:23:08 +01:00
committed by Benjamin Nauck
parent 6cd828d986
commit 1f5e0f6494
5 changed files with 39 additions and 1 deletions

View File

@@ -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: