Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions src/application/ScenarioRunWidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,30 @@

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <optional>
#include <utility>

#include <QColor>
#include <QCoreApplication>
#include <QDialog>
#include <QDoubleSpinBox>
#include <QEventLoop>
#include <QFormLayout>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QIcon>
#include <QLabel>
#include <QMessageBox>
#include <QPainter>
#include <QPaintEvent>
#include <QPen>
#include <QProgressBar>
#include <QPushButton>
#include <QSizePolicy>
#include <QSpinBox>
#include <QTimer>
#include <QVariant>
#include <QVBoxLayout>
Expand All @@ -37,6 +43,7 @@ namespace {
constexpr double kSimulationDeltaSeconds = 1.0 / 30.0;
constexpr double kResultCalculationChunkSeconds = 1.0;
constexpr int kPlaybackTimerIntervalMs = 33;
constexpr int kMaxUiSeed = 2147483647;

int normalizedRunIndex(int index, std::size_t runCount) {
if (runCount == 0) {
Expand All @@ -45,6 +52,18 @@ int normalizedRunIndex(int index, std::size_t runCount) {
return std::clamp(index, 0, static_cast<int>(runCount) - 1);
}

int firstRunIndexForSourceScenario(
const safecrowd::domain::ScenarioBatchRunner& batchRunner,
std::size_t sourceScenarioIndex,
int fallbackIndex) {
for (std::size_t index = 0; index < batchRunner.size(); ++index) {
if (batchRunner.run(index).sourceScenarioIndex == sourceScenarioIndex) {
return static_cast<int>(index);
}
}
return normalizedRunIndex(fallbackIndex, batchRunner.size());
}

enum class TransportIconKind {
Play,
Pause,
Expand Down Expand Up @@ -657,6 +676,49 @@ QWidget* ScenarioRunWidget::createRunPanel() {
layout->addWidget(congestionLabel_);
layout->addWidget(bottleneckLabel_);

auto* settingsTitle = createLabel("Run settings", panel, ui::FontRole::Caption);
settingsTitle->setStyleSheet(ui::mutedTextStyleSheet());
layout->addWidget(settingsTitle);

auto* settingsGroup = new QWidget(panel);
auto* settingsForm = new QFormLayout(settingsGroup);
settingsForm->setContentsMargins(0, 4, 0, 0);
settingsForm->setSpacing(6);

timeLimitSpin_ = new QDoubleSpinBox(settingsGroup);
timeLimitSpin_->setRange(1.0, 3600.0);
timeLimitSpin_->setDecimals(0);
timeLimitSpin_->setSuffix(" s");
timeLimitSpin_->setToolTip("Simulation time limit");
settingsForm->addRow("Time limit", timeLimitSpin_);

sampleIntervalSpin_ = new QDoubleSpinBox(settingsGroup);
sampleIntervalSpin_->setRange(0.1, 10.0);
sampleIntervalSpin_->setSingleStep(0.1);
sampleIntervalSpin_->setDecimals(2);
sampleIntervalSpin_->setSuffix(" s");
sampleIntervalSpin_->setToolTip("Result sample interval");
settingsForm->addRow("Sample interval", sampleIntervalSpin_);

repeatSpin_ = new QSpinBox(settingsGroup);
repeatSpin_->setRange(1, static_cast<int>(safecrowd::domain::kScenarioExecutionMaxRepeatCount));
repeatSpin_->setSuffix(" runs");
repeatSpin_->setToolTip("Repeat count");
settingsForm->addRow("Repeats", repeatSpin_);

seedSpin_ = new QSpinBox(settingsGroup);
seedSpin_->setRange(1, kMaxUiSeed);
seedSpin_->setToolTip("Base random seed");
settingsForm->addRow("Seed", seedSpin_);

applySettingsButton_ = new QPushButton("Apply selected & restart", settingsGroup);
applySettingsButton_->setFont(ui::font(ui::FontRole::Caption));
applySettingsButton_->setStyleSheet(ui::secondaryButtonStyleSheet());
settingsForm->addRow(applySettingsButton_);

layout->addWidget(settingsGroup);
syncRunSettingsControls();

layout->addStretch(1);

auto* transportLayout = new QHBoxLayout();
Expand Down Expand Up @@ -704,6 +766,9 @@ QWidget* ScenarioRunWidget::createRunPanel() {
connect(resultButton_, &QPushButton::clicked, this, [this]() {
showResults();
});
connect(applySettingsButton_, &QPushButton::clicked, this, [this]() {
applyRunSettings();
});

return panel;
}
Expand Down Expand Up @@ -917,7 +982,112 @@ void ScenarioRunWidget::selectRun(int index) {
}
selectedRunIndex_ = index;
scenario_ = batchRunner_.run(static_cast<std::size_t>(selectedRunIndex_)).scenario;
syncRunSettingsControls();
refreshStatus();
}

void ScenarioRunWidget::syncRunSettingsControls() {
if (scenarios_.empty()) {
return;
}
const auto sourceIndex = selectedSourceScenarioIndex();
if (sourceIndex >= scenarios_.size()) {
return;
}
const auto& execution = scenarios_[sourceIndex].execution;
if (timeLimitSpin_ != nullptr) {
timeLimitSpin_->setValue(execution.timeLimitSeconds > 0.0 ? execution.timeLimitSeconds : 600.0);
}
if (sampleIntervalSpin_ != nullptr) {
sampleIntervalSpin_->setValue(execution.sampleIntervalSeconds > 0.0 ? execution.sampleIntervalSeconds : 0.5);
}
if (repeatSpin_ != nullptr) {
repeatSpin_->setValue(std::clamp<int>(
static_cast<int>(execution.repeatCount),
1,
static_cast<int>(safecrowd::domain::kScenarioExecutionMaxRepeatCount)));
}
if (seedSpin_ != nullptr) {
seedSpin_->setValue(execution.baseSeed == 0
? 1
: static_cast<int>(std::min<std::uint32_t>(
execution.baseSeed,
static_cast<std::uint32_t>(kMaxUiSeed))));
}
}

void ScenarioRunWidget::applyRunSettings() {
if (scenarios_.empty()
|| timeLimitSpin_ == nullptr
|| sampleIntervalSpin_ == nullptr
|| repeatSpin_ == nullptr
|| seedSpin_ == nullptr) {
return;
}

const auto sourceIndex = selectedSourceScenarioIndex();
if (sourceIndex >= scenarios_.size()) {
return;
}

const bool wouldDiscardResults = hasCachedResults() || (batchRunner_.complete() && !batchRunner_.empty());
if (wouldDiscardResults
&& QMessageBox::question(
this,
"Apply run settings",
"Re-running with the new settings discards the current simulation results.\n\nContinue?",
QMessageBox::Yes | QMessageBox::Cancel,
QMessageBox::Cancel)
!= QMessageBox::Yes) {
return;
}

auto& execution = scenarios_[sourceIndex].execution;
execution.timeLimitSeconds = timeLimitSpin_->value();
execution.sampleIntervalSeconds = sampleIntervalSpin_->value();
execution.repeatCount = static_cast<std::uint32_t>(repeatSpin_->value());
execution.baseSeed = static_cast<std::uint32_t>(seedSpin_->value());
syncReturnAuthoringScenarioExecution(sourceIndex);

playbackSpeedMultiplier_ = 1;
paused_ = false;
cachedResults_.clear();
if (timer_ != nullptr) {
timer_->stop();
}
batchRunner_.reset(layout_, scenarios_);
selectedRunIndex_ = firstRunIndexForSourceScenario(batchRunner_, sourceIndex, selectedRunIndex_);
if (!batchRunner_.empty()) {
scenario_ = batchRunner_.run(static_cast<std::size_t>(selectedRunIndex_)).scenario;
}
if (shell_ != nullptr) {
shell_->setCanvas(createRunCanvas());
}
refreshStatus();
if (timer_ != nullptr) {
timer_->start();
}
}

void ScenarioRunWidget::syncReturnAuthoringScenarioExecution(std::size_t sourceIndex) {
if (!returnAuthoringState_.has_value() || sourceIndex >= scenarios_.size()) {
return;
}

const auto& sourceScenario = scenarios_[sourceIndex];
auto& authoringScenarios = returnAuthoringState_->scenarios;
auto it = std::find_if(authoringScenarios.begin(), authoringScenarios.end(), [&](const auto& scenario) {
return scenario.draft.scenarioId == sourceScenario.scenarioId;
});
if (it == authoringScenarios.end() && sourceIndex < authoringScenarios.size()) {
it = std::next(authoringScenarios.begin(), static_cast<std::ptrdiff_t>(sourceIndex));
}
if (it == authoringScenarios.end()) {
return;
}

it->draft.execution = sourceScenario.execution;
it->stagedForRun = true;
}

void ScenarioRunWidget::cycleFastForwardMode() {
Expand Down
10 changes: 10 additions & 0 deletions src/application/ScenarioRunWidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
#include "domain/ScenarioAuthoring.h"
#include "domain/ScenarioBatchRunner.h"

class QDoubleSpinBox;
class QLabel;
class QProgressBar;
class QPushButton;
class QSpinBox;
class QTimer;

namespace safecrowd::application {
Expand Down Expand Up @@ -89,6 +91,9 @@ class ScenarioRunWidget : public QWidget {
void refreshStatus();
void selectRun(int index);
std::size_t selectedSourceScenarioIndex() const;
void applyRunSettings();
void syncReturnAuthoringScenarioExecution(std::size_t sourceIndex);
void syncRunSettingsControls();
void showResults();
void setPlaybackSpeedMultiplier(int multiplier);
void stopRun();
Expand Down Expand Up @@ -126,6 +131,11 @@ class ScenarioRunWidget : public QWidget {
QPushButton* speed3Button_{nullptr};
QPushButton* speed5Button_{nullptr};
QPushButton* resultButton_{nullptr};
QDoubleSpinBox* timeLimitSpin_{nullptr};
QDoubleSpinBox* sampleIntervalSpin_{nullptr};
QSpinBox* repeatSpin_{nullptr};
QSpinBox* seedSpin_{nullptr};
QPushButton* applySettingsButton_{nullptr};
int selectedRunIndex_{0};
int playbackSpeedMultiplier_{1};
bool paused_{false};
Expand Down
Loading