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.
This commit is contained in:
Dion Moult
2026-04-22 11:27:53 +10:00
parent 29f5132510
commit 4e4553201b
8 changed files with 103 additions and 4 deletions
+7 -1
View File
@@ -89,6 +89,7 @@ void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_i
}
cancel_requested_ = false;
succeeded_ = false;
running_ = true;
progress_ = 0;
next_object_id_ = start_object_id;
@@ -115,7 +116,11 @@ void GeometryStreamer::loadFile(const std::string& path, uint32_t start_object_i
connect(worker_thread_.get(), &QThread::finished, this, [this]() {
running_ = false;
emit finished();
if (succeeded_.load()) {
emit finished();
} else if (cancel_requested_.load()) {
emit cancelled();
}
});
worker_thread_->start();
@@ -402,4 +407,5 @@ void GeometryStreamer::run(const std::string& path, int num_threads) {
qDebug("Streamer done: %s %.2fs shapes=%u unique_meshes=%u dedup=%.2fx",
path.c_str(), stream_timer.elapsed() / 1000.0,
total_shapes, total_meshes, dedup_ratio);
succeeded_ = !cancel_requested_.load();
}
+2
View File
@@ -68,6 +68,7 @@ signals:
void meshReady(MeshChunk chunk);
void instanceReady(InstanceChunk chunk);
void finished();
void cancelled();
void errorOccurred(const QString& message);
private:
@@ -77,6 +78,7 @@ private:
std::unique_ptr<QThread> worker_thread_;
std::atomic<bool> running_{false};
std::atomic<bool> cancel_requested_{false};
std::atomic<bool> succeeded_{false};
std::atomic<int> progress_{0};
std::mutex elements_mutex_;
+33 -2
View File
@@ -95,10 +95,19 @@ void SceneLoader::connectStreamer(GeometryStreamer* streamer) {
this, &SceneLoader::onStreamerInstanceReady, Qt::QueuedConnection);
connect(streamer, &GeometryStreamer::finished,
this, &SceneLoader::onStreamerFinished, Qt::QueuedConnection);
connect(streamer, &GeometryStreamer::cancelled,
this, &SceneLoader::onStreamerCancelled, Qt::QueuedConnection);
connect(streamer, &GeometryStreamer::errorOccurred,
this, &SceneLoader::onStreamerError, Qt::QueuedConnection);
}
void SceneLoader::cancelCurrentLoad() {
if (loading_model_id_ == 0) return;
auto it = models_.find(loading_model_id_);
if (it == models_.end() || it->second.streamer == nullptr) return;
it->second.streamer->cancel();
}
void SceneLoader::startNextLoad() {
if (load_queue_.empty()) {
loading_model_id_ = 0;
@@ -232,6 +241,28 @@ void SceneLoader::onStreamerFinished() {
startNextLoad();
}
void SceneLoader::onStreamerError(const QString& msg) {
emit loadError(loading_model_id_, msg);
void SceneLoader::onStreamerCancelled() {
element_poll_timer_.stop();
const uint32_t mid = loading_model_id_;
loading_model_id_ = 0;
if (mid != 0) {
viewport_->removeModel(mid);
emit loadCancelled(mid);
}
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
}
void SceneLoader::onStreamerError(const QString& msg) {
element_poll_timer_.stop();
const uint32_t mid = loading_model_id_;
loading_model_id_ = 0;
if (mid != 0) {
viewport_->removeModel(mid);
}
emit loadError(mid, msg);
QTimer::singleShot(0, this, &SceneLoader::startNextLoad);
}
+3
View File
@@ -57,6 +57,7 @@ public:
// Callers can use these to set up per-model UI state (tree roots, etc.)
// before any load signal fires.
std::vector<uint32_t> addFiles(const QStringList& paths);
void cancelCurrentLoad();
bool isLoading() const { return loading_model_id_ != 0 || !load_queue_.empty(); }
size_t modelCount() const { return models_.size(); }
@@ -87,6 +88,7 @@ signals:
// elements to be known (e.g. sidecar write) — SceneLoader will only
// start the next queued load after all slots return.
void loadedFromStream(uint32_t mid, qint64 elapsed_ms);
void loadCancelled(uint32_t mid);
void loadError(uint32_t mid, QString message);
void allLoadsFinished();
@@ -96,6 +98,7 @@ private slots:
void onStreamerMeshReady(MeshChunk chunk);
void onStreamerInstanceReady(InstanceChunk chunk);
void onStreamerFinished();
void onStreamerCancelled();
void onStreamerError(const QString& msg);
void onElementPollTick();