Part: Introduce PreviewUpdateScheduler
This commit introduces PreviewUpdateScheduler class that is responsible to schedule the true recompute of the preview. View Providers (or other components) can use this service to ask for the preview recompute to happend at a time that is convinent for a program and that won't impact performance. The provided implementation uses Queued Connections in Qt to calculate preview essentially on next run of the event loop. It allows business logic in FreeCAD (like property propagation) to execute fully and then recompute preview once. This greately reduces number of recompute calls for previews.
This commit is contained in:
committed by
Chris Hennes
parent
699c539446
commit
d4dc5c01d8
@@ -817,6 +817,9 @@ App::DocumentObject* DocumentObjectWeakPtrT::_get() const noexcept
|
||||
return d->get();
|
||||
}
|
||||
|
||||
DocumentObjectWeakPtrT::DocumentObjectWeakPtrT(DocumentObjectWeakPtrT&&) = default;
|
||||
DocumentObjectWeakPtrT& DocumentObjectWeakPtrT::operator=(DocumentObjectWeakPtrT&&) = default;
|
||||
|
||||
void DocumentObjectWeakPtrT::reset()
|
||||
{
|
||||
d->reset();
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include <fastsignals/signal.h>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <FCGlobal.h>
|
||||
|
||||
|
||||
@@ -372,6 +373,14 @@ public:
|
||||
explicit DocumentObjectWeakPtrT(App::DocumentObject*);
|
||||
~DocumentObjectWeakPtrT();
|
||||
|
||||
// disable copy
|
||||
DocumentObjectWeakPtrT(const DocumentObjectWeakPtrT &) = delete;
|
||||
DocumentObjectWeakPtrT &operator=(const DocumentObjectWeakPtrT &) = delete;
|
||||
|
||||
// default move
|
||||
DocumentObjectWeakPtrT(DocumentObjectWeakPtrT &&);
|
||||
DocumentObjectWeakPtrT &operator=(DocumentObjectWeakPtrT &&);
|
||||
|
||||
/*!
|
||||
* \brief reset
|
||||
* Releases the reference to the managed object. After the call *this manages no object.
|
||||
@@ -417,11 +426,6 @@ public:
|
||||
private:
|
||||
App::DocumentObject* _get() const noexcept;
|
||||
|
||||
public:
|
||||
// disable
|
||||
DocumentObjectWeakPtrT(const DocumentObjectWeakPtrT&) = delete;
|
||||
DocumentObjectWeakPtrT& operator=(const DocumentObjectWeakPtrT&) = delete;
|
||||
|
||||
private:
|
||||
class Private;
|
||||
std::unique_ptr<Private> d;
|
||||
@@ -614,6 +618,15 @@ private:
|
||||
|
||||
} // namespace App
|
||||
|
||||
template<>
|
||||
struct std::hash<App::DocumentObjectWeakPtrT>
|
||||
{
|
||||
std::size_t operator()(const App::DocumentObjectWeakPtrT& ptr) const noexcept
|
||||
{
|
||||
return std::hash<App::DocumentObject*>{}(*ptr);
|
||||
}
|
||||
};
|
||||
|
||||
ENABLE_BITMASK_OPERATORS(App::SubObjectT::NormalizeOption)
|
||||
|
||||
#endif // APP_DOCUMENTOBSERVER_H
|
||||
|
||||
@@ -68,6 +68,34 @@ private:
|
||||
bool _isPreviewFresh {false};
|
||||
};
|
||||
|
||||
/**
|
||||
* Service interface for update scheduler implementation.
|
||||
*
|
||||
* The scheduler manages the timing of preview recomputations. It is designed to debounce
|
||||
* multiple requests—such as those occurring during batch property updates—ensuring that
|
||||
* expensive preview computations are only performed when the system is idle or at a
|
||||
* more convenient time, rather than for every intermediate step.
|
||||
*/
|
||||
class PartExport PreviewUpdateScheduler
|
||||
{
|
||||
public:
|
||||
PreviewUpdateScheduler() = default;
|
||||
virtual ~PreviewUpdateScheduler() = default;
|
||||
|
||||
FC_DISABLE_COPY_MOVE(PreviewUpdateScheduler);
|
||||
|
||||
/**
|
||||
* Schedules a preview recompute for the given object.
|
||||
*
|
||||
* Instead of triggering an immediate update, this method registers the object
|
||||
* with the scheduler. If multiple updates are requested in rapid succession,
|
||||
* the scheduler should collapse them into a single recomputation to improve performance.
|
||||
*
|
||||
* @param object The preview extension of the object that requires an update.
|
||||
*/
|
||||
virtual void schedulePreviewRecompute(App::DocumentObject* object) = 0;
|
||||
};
|
||||
|
||||
template<typename ExtensionT>
|
||||
class PreviewExtensionPythonT: public ExtensionT
|
||||
{
|
||||
|
||||
@@ -26,17 +26,22 @@
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Interpreter.h>
|
||||
#include <Base/PyObjectBase.h>
|
||||
#include <Base/ServiceProvider.h>
|
||||
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/Dialogs/DlgPreferencesImp.h>
|
||||
#include <Gui/WidgetFactory.h>
|
||||
#include <Gui/Language/Translator.h>
|
||||
|
||||
#include <Mod/Part/App/PreviewExtension.h>
|
||||
|
||||
#include "AttacherTexts.h"
|
||||
#include "PropertyEnumAttacherItem.h"
|
||||
#include "DlgSettings3DViewPartImp.h"
|
||||
#include "DlgSettingsGeneral.h"
|
||||
#include "DlgSettingsObjectColor.h"
|
||||
#include "PreviewUpdateScheduler.h"
|
||||
#include "SoBrepEdgeSet.h"
|
||||
#include "SoBrepFaceSet.h"
|
||||
#include "SoBrepPointSet.h"
|
||||
@@ -227,6 +232,8 @@ PyMOD_INIT_FUNC(PartGui)
|
||||
auto manip = std::make_shared<PartGui::WorkbenchManipulator>();
|
||||
Gui::WorkbenchManipulator::installManipulator(manip);
|
||||
|
||||
Base::registerServiceImplementation<Part::PreviewUpdateScheduler>(new PartGui::QtPreviewUpdateScheduler);
|
||||
|
||||
// instantiating the commands
|
||||
CreatePartCommands();
|
||||
CreateSimplePartCommands();
|
||||
|
||||
@@ -142,6 +142,8 @@ SET(PartGui_SRCS
|
||||
PatternParametersWidget.ui
|
||||
Resources/Part.qrc
|
||||
PreCompiled.h
|
||||
PreviewUpdateScheduler.cpp
|
||||
PreviewUpdateScheduler.h
|
||||
PropertyEnumAttacherItem.cpp
|
||||
PropertyEnumAttacherItem.h
|
||||
SoFCShapeObject.cpp
|
||||
|
||||
64
src/Mod/Part/Gui/PreviewUpdateScheduler.cpp
Normal file
64
src/Mod/Part/Gui/PreviewUpdateScheduler.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2026 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreviewUpdateScheduler.h"
|
||||
|
||||
using namespace PartGui;
|
||||
|
||||
QtPreviewUpdateScheduler::QtPreviewUpdateScheduler(QObject* parent)
|
||||
: QObject(parent)
|
||||
{}
|
||||
|
||||
inline void QtPreviewUpdateScheduler::schedulePreviewRecompute(App::DocumentObject* object)
|
||||
{
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
|
||||
toBeUpdated.emplace(object);
|
||||
|
||||
// if method call was already scheduled there is no need to queue another one
|
||||
if (scheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(this, &QtPreviewUpdateScheduler::flush, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void QtPreviewUpdateScheduler::flush()
|
||||
{
|
||||
scheduled = false;
|
||||
|
||||
// use std::exchange to prevent race conditions on updates that could occur during a flush
|
||||
for (auto objects = std::exchange(this->toBeUpdated, {}); auto& object : objects) {
|
||||
if (object.expired()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto* previewExtension = object->getExtensionByType<Part::PreviewExtension>(true)) {
|
||||
previewExtension->updatePreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_PreviewUpdateScheduler.cpp"
|
||||
71
src/Mod/Part/Gui/PreviewUpdateScheduler.h
Normal file
71
src/Mod/Part/Gui/PreviewUpdateScheduler.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2026 Kacper Donat <kacper@kadet.net> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef FREECAD_PREVIEWUPDATESCHEDULER_H
|
||||
#define FREECAD_PREVIEWUPDATESCHEDULER_H
|
||||
|
||||
#include "App/DocumentObserver.h"
|
||||
|
||||
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include <Mod/Part/App/PreviewExtension.h>
|
||||
|
||||
namespace PartGui
|
||||
{
|
||||
|
||||
/**
|
||||
* Qt-based implementation of the PreviewUpdateScheduler.
|
||||
*
|
||||
* This implementation uses the Qt Event Loop to debounce recompute requests.
|
||||
* Requests are queued and a flush is triggered via a queued connection,
|
||||
* ensuring that the actual update happens once the control returns to the
|
||||
* event loop after all pending property changes are processed.
|
||||
*/
|
||||
class QtPreviewUpdateScheduler final: public QObject, public Part::PreviewUpdateScheduler
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QtPreviewUpdateScheduler(QObject* parent = nullptr);
|
||||
|
||||
/**
|
||||
* Schedules a preview recompute using the Qt Event Loop.
|
||||
*
|
||||
* Adds the object to a unique set to avoid duplicate work and schedules
|
||||
* a call to flush() using Qt::QueuedConnection if one isn't already pending.
|
||||
*/
|
||||
void schedulePreviewRecompute(App::DocumentObject* object) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void flush();
|
||||
|
||||
private:
|
||||
std::unordered_set<App::DocumentObjectWeakPtrT> toBeUpdated;
|
||||
bool scheduled = false;
|
||||
};
|
||||
|
||||
} // namespace PartGui
|
||||
|
||||
#endif // FREECAD_PREVIEWUPDATESCHEDULER_H
|
||||
@@ -215,10 +215,15 @@ void ViewProvider::updateData(const App::Property* prop)
|
||||
}
|
||||
else if (auto* previewExtension = getObject()->getExtensionByType<Part::PreviewExtension>(true)) {
|
||||
if (!previewExtension->isPreviewFresh() && isEditing()) {
|
||||
previewExtension->updatePreview();
|
||||
// Properties can be updated in batches, where some properties trigger other updates.
|
||||
// We don't need to compute the preview for intermediate steps. Instead of updating
|
||||
// the preview immediately (and potentially doing it multiple times in a row), we
|
||||
// schedule the update to happen at a more convenient time.
|
||||
if (auto* scheduler = Base::provideService<Part::PreviewUpdateScheduler>()) {
|
||||
scheduler->schedulePreviewRecompute(getObject());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inherited::updateData(prop);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user