mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-10 22:16:41 +00:00
ifcviewer: load rdb/ifc as property data source on sidecar hit
Sidecar hits skipped opening the underlying .rdb/.ifc, so ifcFile() was null and the property panel only showed cached name/type/guid. Now, after a sidecar hit, a background thread opens <stem>.rdb (preferred) or <stem>.ifc and hands the file to GeometryStreamer via setIfcFile(), with a dataSourceReady signal so the UI refreshes the current selection. Gated behind a new AppSettings::loadDataSource toggle (default on) so users can opt into geometry-only viewing; when off, the sidecar-hit thread is skipped and the stream-path ifc_file_ is released after the sidecar write completes. Also adds *.ifcview to the Add Files dialog filter so a cache can be opened directly without its source file present. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,8 @@ MainWindow::MainWindow(QWidget* parent)
|
|||||||
this, &MainWindow::onSidecarElementsReady);
|
this, &MainWindow::onSidecarElementsReady);
|
||||||
connect(loader_, &SceneLoader::loadedFromSidecar,
|
connect(loader_, &SceneLoader::loadedFromSidecar,
|
||||||
this, &MainWindow::onLoadedFromSidecar);
|
this, &MainWindow::onLoadedFromSidecar);
|
||||||
|
connect(loader_, &SceneLoader::dataSourceReady,
|
||||||
|
this, &MainWindow::onDataSourceReady);
|
||||||
connect(loader_, &SceneLoader::streamedElementsReady,
|
connect(loader_, &SceneLoader::streamedElementsReady,
|
||||||
this, &MainWindow::onStreamedElementsReady);
|
this, &MainWindow::onStreamedElementsReady);
|
||||||
connect(loader_, &SceneLoader::loadedFromStream,
|
connect(loader_, &SceneLoader::loadedFromStream,
|
||||||
@@ -141,7 +143,9 @@ void MainWindow::setupMenus() {
|
|||||||
void MainWindow::onFileOpen() {
|
void MainWindow::onFileOpen() {
|
||||||
QStringList paths = QFileDialog::getOpenFileNames(
|
QStringList paths = QFileDialog::getOpenFileNames(
|
||||||
this, "Add IFC Files", QString(),
|
this, "Add IFC Files", QString(),
|
||||||
"IFC Files (*.ifc *.ifcxml *.ifczip);;All Files (*)");
|
"IFC Files (*.ifc *.ifcxml *.ifczip);;"
|
||||||
|
"IFC Viewer Cache (*.ifcview);;"
|
||||||
|
"All Files (*)");
|
||||||
if (!paths.isEmpty()) {
|
if (!paths.isEmpty()) {
|
||||||
addFiles(paths);
|
addFiles(paths);
|
||||||
}
|
}
|
||||||
@@ -254,6 +258,18 @@ void MainWindow::onSidecarElementsReady(uint32_t mid,
|
|||||||
qDebug(" Tree build: %lld ms (%zu elements)", t.elapsed(), elements.size());
|
qDebug(" Tree build: %lld ms (%zu elements)", t.elapsed(), elements.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::onDataSourceReady(uint32_t mid) {
|
||||||
|
// Re-populate if the current selection belongs to this model, since
|
||||||
|
// populateProperties() now has an ifcFile() to query.
|
||||||
|
auto items = element_tree_->selectedItems();
|
||||||
|
if (items.isEmpty()) return;
|
||||||
|
uint32_t object_id = items.first()->data(0, Qt::UserRole).toUInt();
|
||||||
|
auto it = element_map_.find(object_id);
|
||||||
|
if (it != element_map_.end() && it->second.model_id == mid) {
|
||||||
|
populateProperties(object_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::onLoadedFromSidecar(uint32_t /*mid*/, qint64 elapsed_ms) {
|
void MainWindow::onLoadedFromSidecar(uint32_t /*mid*/, qint64 elapsed_ms) {
|
||||||
progress_bar_->setVisible(false);
|
progress_bar_->setVisible(false);
|
||||||
status_label_->setText(QString("%1 elements across %2 model(s) — loaded from cache in %3")
|
status_label_->setText(QString("%1 elements across %2 model(s) — loaded from cache in %3")
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ private slots:
|
|||||||
std::vector<PackedElementInfo> elements,
|
std::vector<PackedElementInfo> elements,
|
||||||
std::string string_table);
|
std::string string_table);
|
||||||
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
||||||
|
void onDataSourceReady(uint32_t mid);
|
||||||
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
|
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
|
||||||
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
|
||||||
void onLoadCancelled(uint32_t mid);
|
void onLoadCancelled(uint32_t mid);
|
||||||
|
|||||||
@@ -50,6 +50,13 @@ void SettingsWindow::setupUi() {
|
|||||||
"closed solids; disable if you see holes in open geometry.");
|
"closed solids; disable if you see holes in open geometry.");
|
||||||
form->addRow("Backface Culling", backface_culling_check_);
|
form->addRow("Backface Culling", backface_culling_check_);
|
||||||
|
|
||||||
|
load_data_source_check_ = new QCheckBox(this);
|
||||||
|
load_data_source_check_->setToolTip(
|
||||||
|
"Keep the .ifc/.rdb open after loading so element properties can "
|
||||||
|
"be queried. Disable for geometry-only viewing — saves memory "
|
||||||
|
"and, on sidecar hits, avoids a second file read.");
|
||||||
|
form->addRow("Load Property Data Source", load_data_source_check_);
|
||||||
|
|
||||||
auto* button_box = new QDialogButtonBox(
|
auto* button_box = new QDialogButtonBox(
|
||||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
|
||||||
@@ -72,11 +79,13 @@ void SettingsWindow::syncFromSettings() {
|
|||||||
geometry_library_edit_->setText(AppSettings::instance().geometryLibrary());
|
geometry_library_edit_->setText(AppSettings::instance().geometryLibrary());
|
||||||
show_stats_check_->setChecked(AppSettings::instance().showStats());
|
show_stats_check_->setChecked(AppSettings::instance().showStats());
|
||||||
backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling());
|
backface_culling_check_->setChecked(AppSettings::instance().backfaceCulling());
|
||||||
|
load_data_source_check_->setChecked(AppSettings::instance().loadDataSource());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsWindow::onAccepted() {
|
void SettingsWindow::onAccepted() {
|
||||||
AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text());
|
AppSettings::instance().setGeometryLibrary(geometry_library_edit_->text());
|
||||||
AppSettings::instance().setShowStats(show_stats_check_->isChecked());
|
AppSettings::instance().setShowStats(show_stats_check_->isChecked());
|
||||||
AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked());
|
AppSettings::instance().setBackfaceCulling(backface_culling_check_->isChecked());
|
||||||
|
AppSettings::instance().setLoadDataSource(load_data_source_check_->isChecked());
|
||||||
accept();
|
accept();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ private:
|
|||||||
QLineEdit* geometry_library_edit_ = nullptr;
|
QLineEdit* geometry_library_edit_ = nullptr;
|
||||||
QCheckBox* show_stats_check_ = nullptr;
|
QCheckBox* show_stats_check_ = nullptr;
|
||||||
QCheckBox* backface_culling_check_ = nullptr;
|
QCheckBox* backface_culling_check_ = nullptr;
|
||||||
|
QCheckBox* load_data_source_check_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ constexpr const char* kGeometryLibraryKey = "geometry/library";
|
|||||||
constexpr const char* kGeometryLibraryDefault = "hybrid-cgal-simple-opencascade";
|
constexpr const char* kGeometryLibraryDefault = "hybrid-cgal-simple-opencascade";
|
||||||
constexpr const char* kShowStatsKey = "viewport/show_stats";
|
constexpr const char* kShowStatsKey = "viewport/show_stats";
|
||||||
constexpr const char* kBackfaceCullingKey = "viewport/backface_culling";
|
constexpr const char* kBackfaceCullingKey = "viewport/backface_culling";
|
||||||
|
constexpr const char* kLoadDataSourceKey = "loading/load_data_source";
|
||||||
}
|
}
|
||||||
|
|
||||||
AppSettings& AppSettings::instance() {
|
AppSettings& AppSettings::instance() {
|
||||||
@@ -70,11 +71,23 @@ void AppSettings::setBackfaceCulling(bool value) {
|
|||||||
emit backfaceCullingChanged(value);
|
emit backfaceCullingChanged(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AppSettings::loadDataSource() const {
|
||||||
|
return load_data_source_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppSettings::setLoadDataSource(bool value) {
|
||||||
|
if (load_data_source_ == value) return;
|
||||||
|
load_data_source_ = value;
|
||||||
|
persist();
|
||||||
|
emit loadDataSourceChanged(value);
|
||||||
|
}
|
||||||
|
|
||||||
void AppSettings::load() {
|
void AppSettings::load() {
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString();
|
geometry_library_ = settings.value(kGeometryLibraryKey, kGeometryLibraryDefault).toString();
|
||||||
show_stats_ = settings.value(kShowStatsKey, false).toBool();
|
show_stats_ = settings.value(kShowStatsKey, false).toBool();
|
||||||
backface_culling_ = settings.value(kBackfaceCullingKey, true).toBool();
|
backface_culling_ = settings.value(kBackfaceCullingKey, true).toBool();
|
||||||
|
load_data_source_ = settings.value(kLoadDataSourceKey, true).toBool();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppSettings::persist() {
|
void AppSettings::persist() {
|
||||||
@@ -82,4 +95,5 @@ void AppSettings::persist() {
|
|||||||
settings.setValue(kGeometryLibraryKey, geometry_library_);
|
settings.setValue(kGeometryLibraryKey, geometry_library_);
|
||||||
settings.setValue(kShowStatsKey, show_stats_);
|
settings.setValue(kShowStatsKey, show_stats_);
|
||||||
settings.setValue(kBackfaceCullingKey, backface_culling_);
|
settings.setValue(kBackfaceCullingKey, backface_culling_);
|
||||||
|
settings.setValue(kLoadDataSourceKey, load_data_source_);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,18 @@ public:
|
|||||||
bool backfaceCulling() const;
|
bool backfaceCulling() const;
|
||||||
void setBackfaceCulling(bool value);
|
void setBackfaceCulling(bool value);
|
||||||
|
|
||||||
|
// When true, the IFC/RocksDB file is kept open (and, on sidecar hits,
|
||||||
|
// opened in the background) so element properties can be queried.
|
||||||
|
// When false, only geometry is loaded — saves memory and avoids a
|
||||||
|
// second file read on sidecar hits, at the cost of no property panel.
|
||||||
|
bool loadDataSource() const;
|
||||||
|
void setLoadDataSource(bool value);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void geometryLibraryChanged(const QString& value);
|
void geometryLibraryChanged(const QString& value);
|
||||||
void showStatsChanged(bool value);
|
void showStatsChanged(bool value);
|
||||||
void backfaceCullingChanged(bool value);
|
void backfaceCullingChanged(bool value);
|
||||||
|
void loadDataSourceChanged(bool value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AppSettings();
|
AppSettings();
|
||||||
@@ -53,6 +61,7 @@ private:
|
|||||||
QString geometry_library_;
|
QString geometry_library_;
|
||||||
bool show_stats_ = false;
|
bool show_stats_ = false;
|
||||||
bool backface_culling_ = true;
|
bool backface_culling_ = true;
|
||||||
|
bool load_data_source_ = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APPSETTINGS_H
|
#endif // APPSETTINGS_H
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ GeometryStreamer::~GeometryStreamer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GeometryStreamer::setIfcFile(std::unique_ptr<ifcopenshell::file> file) {
|
||||||
|
ifc_file_ = std::move(file);
|
||||||
|
}
|
||||||
|
|
||||||
void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_id, uint32_t model_id, int num_threads) {
|
void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_id, uint32_t model_id, int num_threads) {
|
||||||
if (running_.load()) {
|
if (running_.load()) {
|
||||||
cancel();
|
cancel();
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ public:
|
|||||||
void loadFile(const std::string& path, uint32_t start_object_id, uint32_t model_id, int num_threads = 0);
|
void loadFile(const std::string& path, uint32_t start_object_id, uint32_t model_id, int num_threads = 0);
|
||||||
void cancel();
|
void cancel();
|
||||||
|
|
||||||
|
// Adopt an externally-opened ifcopenshell::file as the data source
|
||||||
|
// (e.g. for the sidecar-hit path, where loadFile never runs). The
|
||||||
|
// streamer must not be running geometry iteration when this is called.
|
||||||
|
void setIfcFile(std::unique_ptr<ifcopenshell::file> file);
|
||||||
|
|
||||||
bool isRunning() const { return running_.load(); }
|
bool isRunning() const { return running_.load(); }
|
||||||
int progress() const { return progress_.load(); }
|
int progress() const { return progress_.load(); }
|
||||||
uint32_t lastObjectId() const { return next_object_id_; }
|
uint32_t lastObjectId() const { return next_object_id_; }
|
||||||
|
|||||||
@@ -18,10 +18,12 @@
|
|||||||
********************************************************************************/
|
********************************************************************************/
|
||||||
|
|
||||||
#include "SceneLoader.h"
|
#include "SceneLoader.h"
|
||||||
|
#include "AppSettings.h"
|
||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
@@ -37,6 +39,7 @@ SceneLoader::SceneLoader(ViewportWindow* viewport, QObject* parent)
|
|||||||
|
|
||||||
SceneLoader::~SceneLoader() {
|
SceneLoader::~SceneLoader() {
|
||||||
joinSidecarThread();
|
joinSidecarThread();
|
||||||
|
joinDataSourceThreads();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneLoader::joinSidecarThread() {
|
void SceneLoader::joinSidecarThread() {
|
||||||
@@ -44,6 +47,13 @@ void SceneLoader::joinSidecarThread() {
|
|||||||
sidecar_read_thread_.join();
|
sidecar_read_thread_.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SceneLoader::joinDataSourceThreads() {
|
||||||
|
for (auto& t : data_source_threads_) {
|
||||||
|
if (t.joinable()) t.join();
|
||||||
|
}
|
||||||
|
data_source_threads_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
QString SceneLoader::filePath(uint32_t mid) const {
|
QString SceneLoader::filePath(uint32_t mid) const {
|
||||||
auto it = models_.find(mid);
|
auto it = models_.find(mid);
|
||||||
return it == models_.end() ? QString() : it->second.file_path;
|
return it == models_.end() ? QString() : it->second.file_path;
|
||||||
@@ -187,10 +197,75 @@ void SceneLoader::applySidecarData(uint32_t mid, SidecarData data) {
|
|||||||
qint64 ms = model.load_timer.elapsed();
|
qint64 ms = model.load_timer.elapsed();
|
||||||
emit loadedFromSidecar(mid, ms);
|
emit loadedFromSidecar(mid, ms);
|
||||||
|
|
||||||
|
startDataSourceLoad(mid);
|
||||||
|
|
||||||
loading_model_id_ = 0;
|
loading_model_id_ = 0;
|
||||||
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
|
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Match SidecarCache.cpp's sidecarPath() stem logic so we resolve the
|
||||||
|
// data-source siblings against the same stem the sidecar was keyed on.
|
||||||
|
static std::string pathStem(const std::string& path) {
|
||||||
|
std::string p = path;
|
||||||
|
while (!p.empty() && (p.back() == '/' || p.back() == '\\')) p.pop_back();
|
||||||
|
auto slash = p.find_last_of("/\\");
|
||||||
|
auto dot = p.find_last_of('.');
|
||||||
|
return (dot != std::string::npos &&
|
||||||
|
(slash == std::string::npos || dot > slash))
|
||||||
|
? p.substr(0, dot)
|
||||||
|
: p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneLoader::startDataSourceLoad(uint32_t mid) {
|
||||||
|
if (!AppSettings::instance().loadDataSource()) return;
|
||||||
|
|
||||||
|
auto it = models_.find(mid);
|
||||||
|
if (it == models_.end()) return;
|
||||||
|
|
||||||
|
std::string original_path = it->second.file_path.toStdString();
|
||||||
|
std::string stem = pathStem(original_path);
|
||||||
|
|
||||||
|
// Prefer RocksDB (foo.rdb) over SPF (foo.ifc) for fast random lookups.
|
||||||
|
QString data_path;
|
||||||
|
const QString rdb_candidate = QString::fromStdString(stem + ".rdb");
|
||||||
|
const QString ifc_candidate = QString::fromStdString(stem + ".ifc");
|
||||||
|
if (QFileInfo::exists(rdb_candidate)) {
|
||||||
|
data_path = rdb_candidate;
|
||||||
|
} else if (QFileInfo::exists(ifc_candidate)) {
|
||||||
|
data_path = ifc_candidate;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string data_path_std = data_path.toStdString();
|
||||||
|
data_source_threads_.emplace_back([this, mid, data_path_std]() {
|
||||||
|
QElapsedTimer t; t.start();
|
||||||
|
std::unique_ptr<ifcopenshell::file> file;
|
||||||
|
try {
|
||||||
|
file = std::make_unique<ifcopenshell::file>(
|
||||||
|
data_path_std, ifcopenshell::FT_AUTODETECT, /*read_only=*/true);
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
qWarning(" Data source load failed: %s (%s)",
|
||||||
|
data_path_std.c_str(), e.what());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
qDebug(" Data source load: %lld ms (%s)", t.elapsed(), data_path_std.c_str());
|
||||||
|
|
||||||
|
auto shared = std::make_shared<std::unique_ptr<ifcopenshell::file>>(std::move(file));
|
||||||
|
QMetaObject::invokeMethod(this, [this, mid, shared]() {
|
||||||
|
auto it = models_.find(mid);
|
||||||
|
if (it == models_.end()) return;
|
||||||
|
auto* streamer = it->second.streamer;
|
||||||
|
if (streamer == nullptr) return;
|
||||||
|
// If the streamer already has a file (e.g. a later stream-fallback
|
||||||
|
// path somehow populated it), don't clobber it.
|
||||||
|
if (streamer->ifcFile() != nullptr) return;
|
||||||
|
streamer->setIfcFile(std::move(*shared));
|
||||||
|
emit dataSourceReady(mid);
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void SceneLoader::onStreamerProgressChanged(int percent) {
|
void SceneLoader::onStreamerProgressChanged(int percent) {
|
||||||
emit progressChanged(percent);
|
emit progressChanged(percent);
|
||||||
}
|
}
|
||||||
@@ -227,6 +302,13 @@ void SceneLoader::onStreamerFinished() {
|
|||||||
|
|
||||||
qint64 ms = it->second.load_timer.elapsed();
|
qint64 ms = it->second.load_timer.elapsed();
|
||||||
emit loadedFromStream(mid, ms);
|
emit loadedFromStream(mid, ms);
|
||||||
|
|
||||||
|
// Slot(s) above run synchronously (sidecar write uses element_map_,
|
||||||
|
// not ifcFile()); drop the parsed file now to save memory if the
|
||||||
|
// user has opted out of keeping a property data source.
|
||||||
|
if (!AppSettings::instance().loadDataSource()) {
|
||||||
|
it->second.streamer->setIfcFile(nullptr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,11 @@ signals:
|
|||||||
std::string string_table);
|
std::string string_table);
|
||||||
void loadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
void loadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
|
||||||
|
|
||||||
|
// Fired after a sidecar-hit model has its .rdb/.ifc opened as a
|
||||||
|
// property data source in the background. Consumers can refresh
|
||||||
|
// any UI that queries ifcFile(mid) for attributes/properties.
|
||||||
|
void dataSourceReady(uint32_t mid);
|
||||||
|
|
||||||
// Fired repeatedly while streaming, as the worker thread produces
|
// Fired repeatedly while streaming, as the worker thread produces
|
||||||
// elements. Each batch contains whatever accumulated since the last
|
// elements. Each batch contains whatever accumulated since the last
|
||||||
// poll tick.
|
// poll tick.
|
||||||
@@ -113,7 +118,9 @@ private:
|
|||||||
void startNextLoad();
|
void startNextLoad();
|
||||||
void connectStreamer(GeometryStreamer* streamer);
|
void connectStreamer(GeometryStreamer* streamer);
|
||||||
void joinSidecarThread();
|
void joinSidecarThread();
|
||||||
|
void joinDataSourceThreads();
|
||||||
void applySidecarData(uint32_t mid, SidecarData data);
|
void applySidecarData(uint32_t mid, SidecarData data);
|
||||||
|
void startDataSourceLoad(uint32_t mid);
|
||||||
|
|
||||||
ViewportWindow* viewport_ = nullptr;
|
ViewportWindow* viewport_ = nullptr;
|
||||||
std::map<uint32_t, Entry> models_;
|
std::map<uint32_t, Entry> models_;
|
||||||
@@ -122,6 +129,10 @@ private:
|
|||||||
uint32_t next_object_id_ = 1;
|
uint32_t next_object_id_ = 1;
|
||||||
uint32_t loading_model_id_ = 0;
|
uint32_t loading_model_id_ = 0;
|
||||||
std::thread sidecar_read_thread_;
|
std::thread sidecar_read_thread_;
|
||||||
|
// One thread per sidecar-hit model while its .rdb/.ifc opens in the
|
||||||
|
// background. Joined only at destruction so a slow SPF parse on model
|
||||||
|
// A never blocks the sidecar-hit path of model B.
|
||||||
|
std::vector<std::thread> data_source_threads_;
|
||||||
QTimer element_poll_timer_;
|
QTimer element_poll_timer_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user