Skip to content

Commit 4e45532

Browse files
committed
Fix viewer load termination
Handle streamer success, failure, and cancellation as distinct terminal states so failed or cancelled loads do not finalize as successful models. Clean up partial model/UI state in the full and minimal viewer apps when a load is cancelled or fails. Generated with the assistance of an AI coding tool.
1 parent 29f5132 commit 4e45532

8 files changed

Lines changed: 102 additions & 3 deletions

File tree

src/ifcviewer-full/MainWindow.cpp

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ MainWindow::MainWindow(QWidget* parent)
5353
this, &MainWindow::onStreamedElementsReady);
5454
connect(loader_, &SceneLoader::loadedFromStream,
5555
this, &MainWindow::onLoadedFromStream);
56+
connect(loader_, &SceneLoader::loadCancelled,
57+
this, &MainWindow::onLoadCancelled);
5658
connect(loader_, &SceneLoader::loadError,
5759
this, &MainWindow::onLoadError);
5860
connect(loader_, &SceneLoader::allLoadsFinished,
@@ -297,6 +299,42 @@ void MainWindow::writeSidecarForModel(uint32_t mid) {
297299
qDebug(" Sidecar write: %lld ms (%s)", t.elapsed(), ok ? "ok" : "FAILED");
298300
}
299301

302+
void MainWindow::removeModelUi(uint32_t mid) {
303+
auto root_it = tree_roots_.find(mid);
304+
if (root_it != tree_roots_.end()) {
305+
delete root_it->second;
306+
tree_roots_.erase(root_it);
307+
}
308+
309+
for (auto it = tree_items_.begin(); it != tree_items_.end();) {
310+
auto info_it = element_map_.find(it->first);
311+
if (info_it != element_map_.end() && info_it->second.model_id == mid) {
312+
it = tree_items_.erase(it);
313+
} else {
314+
++it;
315+
}
316+
}
317+
318+
for (auto it = element_map_.begin(); it != element_map_.end();) {
319+
if (it->second.model_id == mid) {
320+
it = element_map_.erase(it);
321+
} else {
322+
++it;
323+
}
324+
}
325+
326+
for (auto it = scoped_ifc_id_to_object_id_.begin(); it != scoped_ifc_id_to_object_id_.end();) {
327+
if (static_cast<uint32_t>(it->first >> 32) == mid) {
328+
it = scoped_ifc_id_to_object_id_.erase(it);
329+
} else {
330+
++it;
331+
}
332+
}
333+
334+
viewport_->setSelectedObjectId(0);
335+
property_table_->setRowCount(0);
336+
}
337+
300338
void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
301339
progress_bar_->setVisible(false);
302340
status_label_->setText(QString("%1 elements across %2 model(s) — last loaded in %3")
@@ -307,7 +345,16 @@ void MainWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
307345
writeSidecarForModel(mid);
308346
}
309347

310-
void MainWindow::onLoadError(uint32_t /*mid*/, QString message) {
348+
void MainWindow::onLoadCancelled(uint32_t mid) {
349+
progress_bar_->setVisible(false);
350+
removeModelUi(mid);
351+
status_label_->setText(QString("%1 load cancelled").arg(loader_->displayName(mid)));
352+
}
353+
354+
void MainWindow::onLoadError(uint32_t mid, QString message) {
355+
progress_bar_->setVisible(false);
356+
removeModelUi(mid);
357+
status_label_->setText("Error: " + message);
311358
QMessageBox::warning(this, "Error", message);
312359
}
313360

src/ifcviewer-full/MainWindow.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ private slots:
6060
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
6161
void onStreamedElementsReady(uint32_t mid, std::vector<ElementInfo> elements);
6262
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
63+
void onLoadCancelled(uint32_t mid);
6364
void onLoadError(uint32_t mid, QString message);
6465
void onAllLoadsFinished();
6566

@@ -75,6 +76,7 @@ private slots:
7576
const std::string& name,
7677
const std::string& type);
7778
void writeSidecarForModel(uint32_t mid);
79+
void removeModelUi(uint32_t mid);
7880
void applyPendingBenchmark();
7981
QString formatElapsed(qint64 ms) const;
8082

src/ifcviewer-minimal/MinimalWindow.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ MinimalWindow::MinimalWindow(QWidget* parent)
4545
this, &MinimalWindow::onLoadedFromSidecar);
4646
connect(loader_, &SceneLoader::loadedFromStream,
4747
this, &MinimalWindow::onLoadedFromStream);
48+
connect(loader_, &SceneLoader::loadCancelled,
49+
this, &MinimalWindow::onLoadCancelled);
4850
connect(loader_, &SceneLoader::loadError,
4951
this, &MinimalWindow::onLoadError);
5052
connect(loader_, &SceneLoader::allLoadsFinished,
@@ -100,6 +102,11 @@ void MinimalWindow::onLoadedFromStream(uint32_t mid, qint64 elapsed_ms) {
100102
.arg(formatElapsed(elapsed_ms)));
101103
}
102104

105+
void MinimalWindow::onLoadCancelled(uint32_t mid) {
106+
status_label_->setText(QString("%1 load cancelled")
107+
.arg(loader_->displayName(mid)));
108+
}
109+
103110
void MinimalWindow::onLoadError(uint32_t /*mid*/, QString message) {
104111
qWarning("IfcViewerMinimal error: %s", qPrintable(message));
105112
status_label_->setText("Error: " + message);

src/ifcviewer-minimal/MinimalWindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ private slots:
4040
void onLoadStarted(uint32_t mid, QString display_name);
4141
void onLoadedFromSidecar(uint32_t mid, qint64 elapsed_ms);
4242
void onLoadedFromStream(uint32_t mid, qint64 elapsed_ms);
43+
void onLoadCancelled(uint32_t mid);
4344
void onLoadError(uint32_t mid, QString message);
4445
void onAllLoadsFinished();
4546

src/ifcviewer/GeometryStreamer.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_i
8989
}
9090

9191
cancel_requested_ = false;
92+
succeeded_ = false;
9293
running_ = true;
9394
progress_ = 0;
9495
next_object_id_ = start_object_id;
@@ -115,7 +116,11 @@ void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_i
115116

116117
connect(worker_thread_.get(), &QThread::finished, this, [this]() {
117118
running_ = false;
118-
emit finished();
119+
if (succeeded_.load()) {
120+
emit finished();
121+
} else if (cancel_requested_.load()) {
122+
emit cancelled();
123+
}
119124
});
120125

121126
worker_thread_->start();
@@ -402,4 +407,5 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
402407
qDebug("Streamer done: %s %.2fs shapes=%u unique_meshes=%u dedup=%.2fx",
403408
path.c_str(), stream_timer.elapsed() / 1000.0,
404409
total_shapes, total_meshes, dedup_ratio);
410+
succeeded_ = !cancel_requested_.load();
405411
}

src/ifcviewer/GeometryStreamer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class GeometryStreamer : public QObject {
6868
void meshReady(MeshChunk chunk);
6969
void instanceReady(InstanceChunk chunk);
7070
void finished();
71+
void cancelled();
7172
void errorOccurred(const QString& message);
7273

7374
private:
@@ -77,6 +78,7 @@ class GeometryStreamer : public QObject {
7778
std::unique_ptr<QThread> worker_thread_;
7879
std::atomic<bool> running_{false};
7980
std::atomic<bool> cancel_requested_{false};
81+
std::atomic<bool> succeeded_{false};
8082
std::atomic<int> progress_{0};
8183

8284
std::mutex elements_mutex_;

src/ifcviewer/SceneLoader.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,19 @@ void SceneLoader::connectStreamer(GeometryStreamer* streamer) {
9595
this, &SceneLoader::onStreamerInstanceReady, Qt::QueuedConnection);
9696
connect(streamer, &GeometryStreamer::finished,
9797
this, &SceneLoader::onStreamerFinished, Qt::QueuedConnection);
98+
connect(streamer, &GeometryStreamer::cancelled,
99+
this, &SceneLoader::onStreamerCancelled, Qt::QueuedConnection);
98100
connect(streamer, &GeometryStreamer::errorOccurred,
99101
this, &SceneLoader::onStreamerError, Qt::QueuedConnection);
100102
}
101103

104+
void SceneLoader::cancelCurrentLoad() {
105+
if (loading_model_id_ == 0) return;
106+
auto it = models_.find(loading_model_id_);
107+
if (it == models_.end() || it->second.streamer == nullptr) return;
108+
it->second.streamer->cancel();
109+
}
110+
102111
void SceneLoader::startNextLoad() {
103112
if (load_queue_.empty()) {
104113
loading_model_id_ = 0;
@@ -232,6 +241,28 @@ void SceneLoader::onStreamerFinished() {
232241
startNextLoad();
233242
}
234243

244+
void SceneLoader::onStreamerCancelled() {
245+
element_poll_timer_.stop();
246+
247+
const uint32_t mid = loading_model_id_;
248+
loading_model_id_ = 0;
249+
250+
if (mid != 0) {
251+
viewport_->removeModel(mid);
252+
emit loadCancelled(mid);
253+
}
254+
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
255+
}
256+
235257
void SceneLoader::onStreamerError(const QString& msg) {
236-
emit loadError(loading_model_id_, msg);
258+
element_poll_timer_.stop();
259+
260+
const uint32_t mid = loading_model_id_;
261+
loading_model_id_ = 0;
262+
263+
if (mid != 0) {
264+
viewport_->removeModel(mid);
265+
}
266+
emit loadError(mid, msg);
267+
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
237268
}

src/ifcviewer/SceneLoader.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class SceneLoader : public QObject {
5757
// Callers can use these to set up per-model UI state (tree roots, etc.)
5858
// before any load signal fires.
5959
std::vector<uint32_t> addFiles(const QStringList& paths);
60+
void cancelCurrentLoad();
6061
bool isLoading() const { return loading_model_id_ != 0 || !load_queue_.empty(); }
6162
size_t modelCount() const { return models_.size(); }
6263

@@ -87,6 +88,7 @@ class SceneLoader : public QObject {
8788
// elements to be known (e.g. sidecar write) — SceneLoader will only
8889
// start the next queued load after all slots return.
8990
void loadedFromStream(uint32_t mid, qint64 elapsed_ms);
91+
void loadCancelled(uint32_t mid);
9092

9193
void loadError(uint32_t mid, QString message);
9294
void allLoadsFinished();
@@ -96,6 +98,7 @@ private slots:
9698
void onStreamerMeshReady(MeshChunk chunk);
9799
void onStreamerInstanceReady(InstanceChunk chunk);
98100
void onStreamerFinished();
101+
void onStreamerCancelled();
99102
void onStreamerError(const QString& msg);
100103
void onElementPollTick();
101104

0 commit comments

Comments
 (0)