Skip to content

Commit a2c8581

Browse files
committed
[Application] Improve project save/load result persistence
1 parent 4051497 commit a2c8581

8 files changed

Lines changed: 256 additions & 7 deletions

src/application/MainWindow.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ ProjectWorkspaceState makeEvacuationScenarioDemoWorkspace() {
7878
.frame = std::move(fixture.frame),
7979
.risk = std::move(fixture.risk),
8080
.artifacts = std::move(fixture.artifacts),
81+
.navigationView = SavedResultNavigationView::Bottleneck,
8182
};
8283
return workspace;
8384
}
@@ -428,6 +429,7 @@ void MainWindow::openProject(const ProjectMetadata& metadata) {
428429
workspace.result->frame,
429430
workspace.result->risk,
430431
workspace.result->artifacts,
432+
workspace.result->navigationView,
431433
workspace.authoring.has_value()
432434
? std::make_optional(initialStateFromSaved(*workspace.authoring, *importResult.layout))
433435
: std::nullopt);
@@ -474,10 +476,7 @@ void MainWindow::saveCurrentProject() {
474476
}
475477
}
476478

477-
if (auto* authoringWidget = visibleChild<ScenarioAuthoringWidget>(centralWidget())) {
478-
workspace.activeView = ProjectWorkspaceView::ScenarioAuthoring;
479-
workspace.authoring = authoringWidget->currentSavedState();
480-
} else if (auto* resultWidget = visibleChild<ScenarioResultWidget>(centralWidget())) {
479+
if (auto* resultWidget = visibleChild<ScenarioResultWidget>(centralWidget())) {
481480
workspace.activeView = ProjectWorkspaceView::ScenarioResult;
482481
if (resultWidget->returnAuthoringState().has_value()) {
483482
workspace.authoring = savedStateFromInitial(*resultWidget->returnAuthoringState());
@@ -487,13 +486,17 @@ void MainWindow::saveCurrentProject() {
487486
.frame = resultWidget->frame(),
488487
.risk = resultWidget->risk(),
489488
.artifacts = resultWidget->artifacts(),
489+
.navigationView = resultWidget->currentSavedNavigationView(),
490490
};
491491
} else if (auto* runWidget = visibleChild<ScenarioRunWidget>(centralWidget())) {
492492
workspace.activeView = ProjectWorkspaceView::ScenarioRun;
493493
if (runWidget->returnAuthoringState().has_value()) {
494494
workspace.authoring = savedStateFromInitial(*runWidget->returnAuthoringState());
495495
}
496496
workspace.runningScenario = runWidget->scenario();
497+
} else if (auto* authoringWidget = visibleChild<ScenarioAuthoringWidget>(centralWidget())) {
498+
workspace.activeView = ProjectWorkspaceView::ScenarioAuthoring;
499+
workspace.authoring = authoringWidget->currentSavedState();
497500
}
498501

499502
if (!ProjectPersistence::saveProjectWorkspace(currentProject_, workspace, &errorMessage)) {
@@ -632,6 +635,7 @@ void MainWindow::showScenarioResult(
632635
const safecrowd::domain::SimulationFrame& frame,
633636
const safecrowd::domain::ScenarioRiskSnapshot& risk,
634637
const safecrowd::domain::ScenarioResultArtifacts& artifacts,
638+
SavedResultNavigationView savedNavigationView,
635639
std::optional<ScenarioAuthoringWidget::InitialState> returnAuthoringState) {
636640
setCentralWidget(new ScenarioResultWidget(
637641
currentProject_.name,
@@ -655,6 +659,7 @@ void MainWindow::showScenarioResult(
655659
showLayoutReview(currentProject_);
656660
}
657661
},
662+
savedNavigationView,
658663
std::move(returnAuthoringState),
659664
this));
660665
}

src/application/MainWindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ class MainWindow : public QMainWindow {
4747
const safecrowd::domain::SimulationFrame& frame,
4848
const safecrowd::domain::ScenarioRiskSnapshot& risk,
4949
const safecrowd::domain::ScenarioResultArtifacts& artifacts,
50+
SavedResultNavigationView savedNavigationView = SavedResultNavigationView::Bottleneck,
5051
std::optional<ScenarioAuthoringWidget::InitialState> returnAuthoringState = std::nullopt);
5152

5253
safecrowd::domain::SafeCrowdDomain& domain_;

src/application/ProjectPersistence.cpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,157 @@ safecrowd::domain::ScenarioRiskSnapshot riskSnapshotFromJson(const QJsonObject&
10161016
return risk;
10171017
}
10181018

1019+
QJsonObject densityCellMetricToJson(const safecrowd::domain::DensityCellMetric& cell) {
1020+
QJsonObject object;
1021+
object["center"] = pointArray(cell.center);
1022+
object["cellMin"] = pointArray(cell.cellMin);
1023+
object["cellMax"] = pointArray(cell.cellMax);
1024+
object["floorId"] = QString::fromStdString(cell.floorId);
1025+
object["agentCount"] = static_cast<qint64>(cell.agentCount);
1026+
object["densityPeoplePerSquareMeter"] = cell.densityPeoplePerSquareMeter;
1027+
return object;
1028+
}
1029+
1030+
safecrowd::domain::DensityCellMetric densityCellMetricFromJson(const QJsonObject& object) {
1031+
return {
1032+
.center = pointFromJson(object.value("center")),
1033+
.cellMin = pointFromJson(object.value("cellMin")),
1034+
.cellMax = pointFromJson(object.value("cellMax")),
1035+
.floorId = object.value("floorId").toString().toStdString(),
1036+
.agentCount = static_cast<std::size_t>(object.value("agentCount").toInteger()),
1037+
.densityPeoplePerSquareMeter = object.value("densityPeoplePerSquareMeter").toDouble(),
1038+
};
1039+
}
1040+
1041+
QJsonObject densityFieldSnapshotToJson(const safecrowd::domain::DensityFieldSnapshot& snapshot) {
1042+
QJsonObject object;
1043+
object["timeSeconds"] = snapshot.timeSeconds;
1044+
object["cellSizeMeters"] = snapshot.cellSizeMeters;
1045+
QJsonArray cells;
1046+
for (const auto& cell : snapshot.cells) {
1047+
cells.append(densityCellMetricToJson(cell));
1048+
}
1049+
object["cells"] = cells;
1050+
return object;
1051+
}
1052+
1053+
safecrowd::domain::DensityFieldSnapshot densityFieldSnapshotFromJson(const QJsonObject& object) {
1054+
safecrowd::domain::DensityFieldSnapshot snapshot;
1055+
snapshot.timeSeconds = object.value("timeSeconds").toDouble();
1056+
snapshot.cellSizeMeters = object.value("cellSizeMeters").toDouble();
1057+
for (const auto& value : object.value("cells").toArray()) {
1058+
snapshot.cells.push_back(densityCellMetricFromJson(value.toObject()));
1059+
}
1060+
return snapshot;
1061+
}
1062+
1063+
QJsonObject densitySummaryToJson(const safecrowd::domain::DensitySummary& summary) {
1064+
QJsonObject object;
1065+
object["cellSizeMeters"] = summary.cellSizeMeters;
1066+
object["highDensityThresholdPeoplePerSquareMeter"] = summary.highDensityThresholdPeoplePerSquareMeter;
1067+
object["peakDensityPeoplePerSquareMeter"] = summary.peakDensityPeoplePerSquareMeter;
1068+
object["peakAgentCount"] = static_cast<qint64>(summary.peakAgentCount);
1069+
object["peakAtSeconds"] = optionalDoubleToJson(summary.peakAtSeconds);
1070+
if (summary.peakCell.has_value()) {
1071+
object["peakCell"] = densityCellMetricToJson(*summary.peakCell);
1072+
}
1073+
object["highDensityDurationSeconds"] = summary.highDensityDurationSeconds;
1074+
QJsonArray peakCells;
1075+
for (const auto& cell : summary.peakCells) {
1076+
peakCells.append(densityCellMetricToJson(cell));
1077+
}
1078+
object["peakCells"] = peakCells;
1079+
object["peakField"] = densityFieldSnapshotToJson(summary.peakField);
1080+
return object;
1081+
}
1082+
1083+
safecrowd::domain::DensitySummary densitySummaryFromJson(const QJsonObject& object) {
1084+
safecrowd::domain::DensitySummary summary;
1085+
summary.cellSizeMeters = object.value("cellSizeMeters").toDouble();
1086+
summary.highDensityThresholdPeoplePerSquareMeter =
1087+
object.value("highDensityThresholdPeoplePerSquareMeter").toDouble(4.0);
1088+
summary.peakDensityPeoplePerSquareMeter = object.value("peakDensityPeoplePerSquareMeter").toDouble();
1089+
summary.peakAgentCount = static_cast<std::size_t>(object.value("peakAgentCount").toInteger());
1090+
summary.peakAtSeconds = optionalDoubleFromJson(object.value("peakAtSeconds"));
1091+
if (object.value("peakCell").isObject()) {
1092+
summary.peakCell = densityCellMetricFromJson(object.value("peakCell").toObject());
1093+
}
1094+
summary.highDensityDurationSeconds = object.value("highDensityDurationSeconds").toDouble();
1095+
for (const auto& value : object.value("peakCells").toArray()) {
1096+
summary.peakCells.push_back(densityCellMetricFromJson(value.toObject()));
1097+
}
1098+
if (object.value("peakField").isObject()) {
1099+
summary.peakField = densityFieldSnapshotFromJson(object.value("peakField").toObject());
1100+
}
1101+
return summary;
1102+
}
1103+
1104+
QJsonObject exitUsageMetricToJson(const safecrowd::domain::ExitUsageMetric& exit) {
1105+
QJsonObject object;
1106+
object["exitZoneId"] = QString::fromStdString(exit.exitZoneId);
1107+
object["exitLabel"] = QString::fromStdString(exit.exitLabel);
1108+
object["floorId"] = QString::fromStdString(exit.floorId);
1109+
object["evacuatedCount"] = static_cast<qint64>(exit.evacuatedCount);
1110+
object["usageRatio"] = exit.usageRatio;
1111+
object["lastExitTimeSeconds"] = optionalDoubleToJson(exit.lastExitTimeSeconds);
1112+
return object;
1113+
}
1114+
1115+
safecrowd::domain::ExitUsageMetric exitUsageMetricFromJson(const QJsonObject& object) {
1116+
safecrowd::domain::ExitUsageMetric exit;
1117+
exit.exitZoneId = object.value("exitZoneId").toString().toStdString();
1118+
exit.exitLabel = object.value("exitLabel").toString().toStdString();
1119+
exit.floorId = object.value("floorId").toString().toStdString();
1120+
exit.evacuatedCount = static_cast<std::size_t>(object.value("evacuatedCount").toInteger());
1121+
exit.usageRatio = object.value("usageRatio").toDouble();
1122+
exit.lastExitTimeSeconds = optionalDoubleFromJson(object.value("lastExitTimeSeconds"));
1123+
return exit;
1124+
}
1125+
1126+
QJsonObject zoneCompletionMetricToJson(const safecrowd::domain::ZoneCompletionMetric& zone) {
1127+
QJsonObject object;
1128+
object["zoneId"] = QString::fromStdString(zone.zoneId);
1129+
object["zoneLabel"] = QString::fromStdString(zone.zoneLabel);
1130+
object["floorId"] = QString::fromStdString(zone.floorId);
1131+
object["initialCount"] = static_cast<qint64>(zone.initialCount);
1132+
object["evacuatedCount"] = static_cast<qint64>(zone.evacuatedCount);
1133+
object["lastCompletionTimeSeconds"] = optionalDoubleToJson(zone.lastCompletionTimeSeconds);
1134+
return object;
1135+
}
1136+
1137+
safecrowd::domain::ZoneCompletionMetric zoneCompletionMetricFromJson(const QJsonObject& object) {
1138+
safecrowd::domain::ZoneCompletionMetric zone;
1139+
zone.zoneId = object.value("zoneId").toString().toStdString();
1140+
zone.zoneLabel = object.value("zoneLabel").toString().toStdString();
1141+
zone.floorId = object.value("floorId").toString().toStdString();
1142+
zone.initialCount = static_cast<std::size_t>(object.value("initialCount").toInteger());
1143+
zone.evacuatedCount = static_cast<std::size_t>(object.value("evacuatedCount").toInteger());
1144+
zone.lastCompletionTimeSeconds = optionalDoubleFromJson(object.value("lastCompletionTimeSeconds"));
1145+
return zone;
1146+
}
1147+
1148+
QJsonObject placementCompletionMetricToJson(const safecrowd::domain::PlacementCompletionMetric& placement) {
1149+
QJsonObject object;
1150+
object["placementId"] = QString::fromStdString(placement.placementId);
1151+
object["zoneId"] = QString::fromStdString(placement.zoneId);
1152+
object["floorId"] = QString::fromStdString(placement.floorId);
1153+
object["initialCount"] = static_cast<qint64>(placement.initialCount);
1154+
object["evacuatedCount"] = static_cast<qint64>(placement.evacuatedCount);
1155+
object["lastCompletionTimeSeconds"] = optionalDoubleToJson(placement.lastCompletionTimeSeconds);
1156+
return object;
1157+
}
1158+
1159+
safecrowd::domain::PlacementCompletionMetric placementCompletionMetricFromJson(const QJsonObject& object) {
1160+
safecrowd::domain::PlacementCompletionMetric placement;
1161+
placement.placementId = object.value("placementId").toString().toStdString();
1162+
placement.zoneId = object.value("zoneId").toString().toStdString();
1163+
placement.floorId = object.value("floorId").toString().toStdString();
1164+
placement.initialCount = static_cast<std::size_t>(object.value("initialCount").toInteger());
1165+
placement.evacuatedCount = static_cast<std::size_t>(object.value("evacuatedCount").toInteger());
1166+
placement.lastCompletionTimeSeconds = optionalDoubleFromJson(object.value("lastCompletionTimeSeconds"));
1167+
return placement;
1168+
}
1169+
10191170
QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifacts& artifacts) {
10201171
QJsonObject object;
10211172
QJsonArray progress;
@@ -1040,13 +1191,36 @@ QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifac
10401191
timing["t90Seconds"] = optionalDoubleToJson(artifacts.timingSummary.t90Seconds);
10411192
timing["t95Seconds"] = optionalDoubleToJson(artifacts.timingSummary.t95Seconds);
10421193
timing["finalEvacuationTimeSeconds"] = optionalDoubleToJson(artifacts.timingSummary.finalEvacuationTimeSeconds);
1194+
timing["targetTimeSeconds"] = artifacts.timingSummary.targetTimeSeconds;
1195+
timing["marginSeconds"] = optionalDoubleToJson(artifacts.timingSummary.marginSeconds);
10431196
if (artifacts.timingSummary.t90Frame.has_value()) {
10441197
timing["t90Frame"] = simulationFrameToJson(*artifacts.timingSummary.t90Frame);
10451198
}
10461199
if (artifacts.timingSummary.t95Frame.has_value()) {
10471200
timing["t95Frame"] = simulationFrameToJson(*artifacts.timingSummary.t95Frame);
10481201
}
10491202
object["timingSummary"] = timing;
1203+
1204+
object["densitySummary"] = densitySummaryToJson(artifacts.densitySummary);
1205+
1206+
QJsonArray exitUsage;
1207+
for (const auto& exit : artifacts.exitUsage) {
1208+
exitUsage.append(exitUsageMetricToJson(exit));
1209+
}
1210+
object["exitUsage"] = exitUsage;
1211+
1212+
QJsonArray zoneCompletion;
1213+
for (const auto& zone : artifacts.zoneCompletion) {
1214+
zoneCompletion.append(zoneCompletionMetricToJson(zone));
1215+
}
1216+
object["zoneCompletion"] = zoneCompletion;
1217+
1218+
QJsonArray placementCompletion;
1219+
for (const auto& placement : artifacts.placementCompletion) {
1220+
placementCompletion.append(placementCompletionMetricToJson(placement));
1221+
}
1222+
object["placementCompletion"] = placementCompletion;
1223+
10501224
return object;
10511225
}
10521226

@@ -1070,12 +1244,28 @@ safecrowd::domain::ScenarioResultArtifacts resultArtifactsFromJson(const QJsonOb
10701244
artifacts.timingSummary.t90Seconds = optionalDoubleFromJson(timing.value("t90Seconds"));
10711245
artifacts.timingSummary.t95Seconds = optionalDoubleFromJson(timing.value("t95Seconds"));
10721246
artifacts.timingSummary.finalEvacuationTimeSeconds = optionalDoubleFromJson(timing.value("finalEvacuationTimeSeconds"));
1247+
artifacts.timingSummary.targetTimeSeconds = timing.value("targetTimeSeconds").toDouble();
1248+
artifacts.timingSummary.marginSeconds = optionalDoubleFromJson(timing.value("marginSeconds"));
10731249
if (timing.value("t90Frame").isObject()) {
10741250
artifacts.timingSummary.t90Frame = simulationFrameFromJson(timing.value("t90Frame").toObject());
10751251
}
10761252
if (timing.value("t95Frame").isObject()) {
10771253
artifacts.timingSummary.t95Frame = simulationFrameFromJson(timing.value("t95Frame").toObject());
10781254
}
1255+
1256+
if (object.value("densitySummary").isObject()) {
1257+
artifacts.densitySummary = densitySummaryFromJson(object.value("densitySummary").toObject());
1258+
}
1259+
for (const auto& value : object.value("exitUsage").toArray()) {
1260+
artifacts.exitUsage.push_back(exitUsageMetricFromJson(value.toObject()));
1261+
}
1262+
for (const auto& value : object.value("zoneCompletion").toArray()) {
1263+
artifacts.zoneCompletion.push_back(zoneCompletionMetricFromJson(value.toObject()));
1264+
}
1265+
for (const auto& value : object.value("placementCompletion").toArray()) {
1266+
artifacts.placementCompletion.push_back(placementCompletionMetricFromJson(value.toObject()));
1267+
}
1268+
10791269
return artifacts;
10801270
}
10811271

@@ -1125,6 +1315,7 @@ QJsonObject resultStateToJson(const SavedScenarioResultState& result) {
11251315
object["frame"] = simulationFrameToJson(result.frame);
11261316
object["risk"] = riskSnapshotToJson(result.risk);
11271317
object["artifacts"] = resultArtifactsToJson(result.artifacts);
1318+
object["navigationView"] = static_cast<int>(result.navigationView);
11281319
return object;
11291320
}
11301321

@@ -1134,6 +1325,7 @@ SavedScenarioResultState resultStateFromJson(const QJsonObject& object) {
11341325
.frame = simulationFrameFromJson(object.value("frame").toObject()),
11351326
.risk = riskSnapshotFromJson(object.value("risk").toObject()),
11361327
.artifacts = resultArtifactsFromJson(object.value("artifacts").toObject()),
1328+
.navigationView = static_cast<SavedResultNavigationView>(object.value("navigationView").toInt()),
11371329
};
11381330
}
11391331

src/application/ProjectWorkspaceState.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ enum class SavedRightPanelMode {
3030
Run,
3131
};
3232

33+
enum class SavedResultNavigationView {
34+
Bottleneck,
35+
Hotspot,
36+
Zone,
37+
Groups,
38+
};
39+
3340
struct SavedScenarioState {
3441
safecrowd::domain::ScenarioDraft draft{};
3542
std::string baseScenarioId{};
@@ -48,6 +55,7 @@ struct SavedScenarioResultState {
4855
safecrowd::domain::SimulationFrame frame{};
4956
safecrowd::domain::ScenarioRiskSnapshot risk{};
5057
safecrowd::domain::ScenarioResultArtifacts artifacts{};
58+
SavedResultNavigationView navigationView{SavedResultNavigationView::Bottleneck};
5159
};
5260

5361
struct ProjectWorkspaceState {

src/application/ScenarioAuthoringWidget.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -607,7 +607,6 @@ ScenarioAuthoringWidget::ScenarioAuthoringWidget(
607607
scenario.stagedForRun = false;
608608
}
609609
}
610-
rightPanelMode_ = RightPanelMode::Scenario;
611610
initializeUi(false);
612611
}
613612

@@ -638,7 +637,18 @@ SavedScenarioAuthoringState ScenarioAuthoringWidget::currentSavedState() const {
638637
SavedScenarioAuthoringState state;
639638
state.currentScenarioIndex = currentScenarioIndex_;
640639
state.navigationView = savedNavigationView(navigationView_);
641-
state.rightPanelMode = SavedRightPanelMode::Scenario;
640+
switch (rightPanelMode_) {
641+
case RightPanelMode::None:
642+
state.rightPanelMode = SavedRightPanelMode::None;
643+
break;
644+
case RightPanelMode::Run:
645+
state.rightPanelMode = SavedRightPanelMode::Run;
646+
break;
647+
case RightPanelMode::Scenario:
648+
default:
649+
state.rightPanelMode = SavedRightPanelMode::Scenario;
650+
break;
651+
}
642652
state.scenarios.reserve(scenarios_.size());
643653
for (const auto& scenario : scenarios_) {
644654
auto draft = scenario.draft;
@@ -1038,7 +1048,6 @@ void ScenarioAuthoringWidget::refreshRightPanel() {
10381048
return;
10391049
}
10401050

1041-
rightPanelMode_ = RightPanelMode::Scenario;
10421051
shell_->setReviewPanelVisible(true);
10431052
shell_->setReviewPanel(createScenarioPanel());
10441053
refreshScenarioSwitcher();

0 commit comments

Comments
 (0)