diff --git a/src/application/ScenarioResultWidget.cpp b/src/application/ScenarioResultWidget.cpp index 19975ec..bdb2094 100644 --- a/src/application/ScenarioResultWidget.cpp +++ b/src/application/ScenarioResultWidget.cpp @@ -1459,6 +1459,9 @@ void ScenarioResultWidget::rerunScenario() { saveProjectHandler_, openProjectHandler_, backToLayoutReviewHandler_, + frame_, + risk_, + artifacts_, this); rootLayout->replaceWidget(shell_, runWidget); diff --git a/src/application/ScenarioRunWidget.cpp b/src/application/ScenarioRunWidget.cpp index d3d8795..2f1d41b 100644 --- a/src/application/ScenarioRunWidget.cpp +++ b/src/application/ScenarioRunWidget.cpp @@ -181,6 +181,35 @@ ScenarioRunWidget::ScenarioRunWidget( saveProjectHandler_(std::move(saveProjectHandler)), openProjectHandler_(std::move(openProjectHandler)), backToLayoutReviewHandler_(std::move(backToLayoutReviewHandler)) { + setupUi(); +} + +ScenarioRunWidget::ScenarioRunWidget( + const QString& projectName, + const safecrowd::domain::FacilityLayout2D& layout, + const safecrowd::domain::ScenarioDraft& scenario, + std::function saveProjectHandler, + std::function openProjectHandler, + std::function backToLayoutReviewHandler, + safecrowd::domain::SimulationFrame cachedResultFrame, + safecrowd::domain::ScenarioRiskSnapshot cachedResultRisk, + safecrowd::domain::ScenarioResultArtifacts cachedResultArtifacts, + QWidget* parent) + : QWidget(parent), + projectName_(projectName), + layout_(layout), + scenario_(scenario), + runner_(layout_, scenario_), + cachedResultFrame_(std::move(cachedResultFrame)), + cachedResultRisk_(std::move(cachedResultRisk)), + cachedResultArtifacts_(std::move(cachedResultArtifacts)), + saveProjectHandler_(std::move(saveProjectHandler)), + openProjectHandler_(std::move(openProjectHandler)), + backToLayoutReviewHandler_(std::move(backToLayoutReviewHandler)) { + setupUi(); +} + +void ScenarioRunWidget::setupUi() { auto* rootLayout = new QVBoxLayout(this); rootLayout->setContentsMargins(0, 0, 0, 0); rootLayout->setSpacing(0); @@ -275,6 +304,12 @@ QWidget* ScenarioRunWidget::createRunPanel() { layout->addStretch(1); + skipResultButton_ = new QPushButton("Skip for Result", panel); + skipResultButton_->setFont(ui::font(ui::FontRole::Body)); + skipResultButton_->setStyleSheet(ui::secondaryButtonStyleSheet()); + skipResultButton_->setEnabled(false); + layout->addWidget(skipResultButton_); + resultButton_ = new QPushButton("View Results", panel); resultButton_->setFont(ui::font(ui::FontRole::Body)); resultButton_->setStyleSheet(ui::primaryButtonStyleSheet()); @@ -287,6 +322,9 @@ QWidget* ScenarioRunWidget::createRunPanel() { connect(stopButton_, &QPushButton::clicked, this, [this]() { stopRun(); }); + connect(skipResultButton_, &QPushButton::clicked, this, [this]() { + runToCompletion(); + }); connect(resultButton_, &QPushButton::clicked, this, [this]() { showResults(); }); @@ -326,6 +364,12 @@ void ScenarioRunWidget::returnToAuthoring() { canvas_ = nullptr; } +bool ScenarioRunWidget::hasCachedResult() const noexcept { + return cachedResultFrame_.has_value() + && cachedResultRisk_.has_value() + && cachedResultArtifacts_.has_value(); +} + void ScenarioRunWidget::refreshStatus() { const auto& frame = runner_.frame(); if (scenarioLabel_ != nullptr) { @@ -390,24 +434,75 @@ void ScenarioRunWidget::refreshStatus() { if (stopButton_ != nullptr) { stopButton_->setEnabled(frame.totalAgentCount > 0); } + if (skipResultButton_ != nullptr) { + skipResultButton_->setEnabled(frame.totalAgentCount > 0 && !frame.complete && !hasCachedResult()); + } if (resultButton_ != nullptr) { - resultButton_->setEnabled(frame.complete && frame.totalAgentCount > 0); + const auto cachedAgentCount = hasCachedResult() ? cachedResultFrame_->totalAgentCount : 0; + resultButton_->setEnabled( + (frame.complete && frame.totalAgentCount > 0) + || cachedAgentCount > 0); + } +} + +bool ScenarioRunWidget::runToCompletion() { + if (timer_ != nullptr) { + timer_->stop(); + } + + const auto remainingSeconds = std::max(0.0, runner_.timeLimitSeconds() - runner_.frame().elapsedSeconds); + const auto maxSteps = static_cast(std::ceil(remainingSeconds / kSimulationDeltaSeconds)) + 2; + for (int step = 0; step < maxSteps && !runner_.complete(); ++step) { + runner_.step(kSimulationDeltaSeconds); + } + + if (canvas_ != nullptr) { + canvas_->setFrame(runner_.frame()); } + if (runner_.complete()) { + storeResultCache(runner_); + } + refreshStatus(); + return runner_.complete(); +} + +void ScenarioRunWidget::storeResultCache(const safecrowd::domain::ScenarioSimulationRunner& runner) { + cachedResultFrame_ = runner.frame(); + cachedResultRisk_ = runner.resultRiskSnapshot(); + cachedResultArtifacts_ = runner.resultArtifacts(); } void ScenarioRunWidget::stopRun() { paused_ = true; runner_.reset(layout_, scenario_); + cachedResultFrame_.reset(); + cachedResultRisk_.reset(); + cachedResultArtifacts_.reset(); canvas_->setFrame(runner_.frame()); refreshStatus(); timer_->start(); } void ScenarioRunWidget::showResults() { - const auto& frame = runner_.frame(); - if (frame.totalAgentCount == 0 || !frame.complete) { + if (runner_.frame().totalAgentCount == 0 && !hasCachedResult()) { return; } + + const auto* resultFrame = &runner_.frame(); + const auto* resultRisk = &runner_.resultRiskSnapshot(); + const auto* resultArtifacts = &runner_.resultArtifacts(); + if (!runner_.complete() && hasCachedResult()) { + resultFrame = &*cachedResultFrame_; + resultRisk = &*cachedResultRisk_; + resultArtifacts = &*cachedResultArtifacts_; + } else if (!runner_.complete()) { + return; + } else { + resultFrame = &runner_.frame(); + resultRisk = &runner_.resultRiskSnapshot(); + resultArtifacts = &runner_.resultArtifacts(); + } + if (timer_ != nullptr) { timer_->stop(); } @@ -421,9 +516,9 @@ void ScenarioRunWidget::showResults() { projectName_, layout_, scenario_, - runner_.frame(), - runner_.resultRiskSnapshot(), - runner_.resultArtifacts(), + *resultFrame, + *resultRisk, + *resultArtifacts, [this]() { if (saveProjectHandler_) { saveProjectHandler_(); diff --git a/src/application/ScenarioRunWidget.h b/src/application/ScenarioRunWidget.h index 2218ba7..f076bc8 100644 --- a/src/application/ScenarioRunWidget.h +++ b/src/application/ScenarioRunWidget.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -29,13 +30,28 @@ class ScenarioRunWidget : public QWidget { std::function openProjectHandler, std::function backToLayoutReviewHandler, QWidget* parent = nullptr); + explicit ScenarioRunWidget( + const QString& projectName, + const safecrowd::domain::FacilityLayout2D& layout, + const safecrowd::domain::ScenarioDraft& scenario, + std::function saveProjectHandler, + std::function openProjectHandler, + std::function backToLayoutReviewHandler, + safecrowd::domain::SimulationFrame cachedResultFrame, + safecrowd::domain::ScenarioRiskSnapshot cachedResultRisk, + safecrowd::domain::ScenarioResultArtifacts cachedResultArtifacts, + QWidget* parent = nullptr); const safecrowd::domain::ScenarioDraft& scenario() const noexcept; private: QWidget* createRunPanel(); void returnToAuthoring(); + bool hasCachedResult() const noexcept; void refreshStatus(); + bool runToCompletion(); + void storeResultCache(const safecrowd::domain::ScenarioSimulationRunner& runner); + void setupUi(); void showResults(); void stopRun(); void togglePaused(); @@ -44,6 +60,9 @@ class ScenarioRunWidget : public QWidget { safecrowd::domain::FacilityLayout2D layout_{}; safecrowd::domain::ScenarioDraft scenario_{}; safecrowd::domain::ScenarioSimulationRunner runner_{}; + std::optional cachedResultFrame_{}; + std::optional cachedResultRisk_{}; + std::optional cachedResultArtifacts_{}; std::function saveProjectHandler_{}; std::function openProjectHandler_{}; std::function backToLayoutReviewHandler_{}; @@ -61,6 +80,7 @@ class ScenarioRunWidget : public QWidget { QLabel* bottleneckLabel_{nullptr}; QPushButton* pauseButton_{nullptr}; QPushButton* stopButton_{nullptr}; + QPushButton* skipResultButton_{nullptr}; QPushButton* resultButton_{nullptr}; bool paused_{false}; };