Skip to content

Commit 51d5a28

Browse files
95x8x9learncold
authored andcommitted
[Application] Improve project save/load result persistence
1 parent 0687e70 commit 51d5a28

11 files changed

Lines changed: 382 additions & 9 deletions

src/application/MainWindow.cpp

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ ProjectWorkspaceState makeEvacuationScenarioDemoWorkspace() {
8181
.frame = std::move(fixture.frame),
8282
.risk = std::move(fixture.risk),
8383
.artifacts = std::move(fixture.artifacts),
84+
.navigationView = SavedResultNavigationView::Bottleneck,
8485
};
8586
workspace.batchResult = SavedScenarioBatchResultState{
8687
.results = {*workspace.result},
@@ -454,6 +455,7 @@ void MainWindow::openProject(const ProjectMetadata& metadata) {
454455
workspace.result->frame,
455456
workspace.result->risk,
456457
workspace.result->artifacts,
458+
workspace.result->navigationView,
457459
workspace.authoring.has_value()
458460
? std::make_optional(initialStateFromSaved(*workspace.authoring, *importResult.layout))
459461
: std::nullopt);
@@ -500,16 +502,55 @@ void MainWindow::saveCurrentProject() {
500502
}
501503
}
502504

503-
if (auto* authoringWidget = visibleChild<ScenarioAuthoringWidget>(centralWidget())) {
505+
auto* authoringWidget = visibleChild<ScenarioAuthoringWidget>(centralWidget());
506+
auto* batchResultWidget = visibleChild<ScenarioBatchResultWidget>(centralWidget());
507+
auto* resultWidget = visibleChild<ScenarioResultWidget>(centralWidget());
508+
auto* runWidget = visibleChild<ScenarioRunWidget>(centralWidget());
509+
510+
const auto rootWidget = centralWidget();
511+
const auto widgetDepth = [rootWidget](QWidget* widget) {
512+
int depth = 0;
513+
for (auto* current = widget; current != nullptr; current = current->parentWidget()) {
514+
if (current == rootWidget) {
515+
return depth;
516+
}
517+
++depth;
518+
}
519+
return -1;
520+
};
521+
522+
QWidget* activeWorkflowWidget = nullptr;
523+
int activeWorkflowDepth = -1;
524+
const auto chooseActiveWorkflowWidget = [&](QWidget* widget) {
525+
const int depth = widgetDepth(widget);
526+
if (depth > activeWorkflowDepth) {
527+
activeWorkflowDepth = depth;
528+
activeWorkflowWidget = widget;
529+
}
530+
};
531+
chooseActiveWorkflowWidget(authoringWidget);
532+
chooseActiveWorkflowWidget(batchResultWidget);
533+
chooseActiveWorkflowWidget(resultWidget);
534+
chooseActiveWorkflowWidget(runWidget);
535+
536+
if (activeWorkflowWidget == authoringWidget) {
504537
workspace.activeView = ProjectWorkspaceView::ScenarioAuthoring;
505538
workspace.authoring = authoringWidget->currentSavedState();
506-
} else if (auto* batchResultWidget = visibleChild<ScenarioBatchResultWidget>(centralWidget())) {
539+
} else if (activeWorkflowWidget == batchResultWidget) {
507540
workspace.activeView = ProjectWorkspaceView::ScenarioResult;
508541
if (auto authoring = batchResultWidget->returnAuthoringState(); authoring.has_value()) {
509542
workspace.authoring = savedStateFromInitial(*authoring);
510543
}
544+
auto results = batchResultWidget->results();
545+
if (!results.empty()) {
546+
const auto index = std::clamp(
547+
batchResultWidget->currentResultIndex(),
548+
0,
549+
static_cast<int>(results.size()) - 1);
550+
results[static_cast<std::size_t>(index)].navigationView = batchResultWidget->currentSavedNavigationView();
551+
}
511552
workspace.batchResult = SavedScenarioBatchResultState{
512-
.results = batchResultWidget->results(),
553+
.results = std::move(results),
513554
.currentResultIndex = batchResultWidget->currentResultIndex(),
514555
};
515556
if (!workspace.batchResult->results.empty()) {
@@ -519,7 +560,7 @@ void MainWindow::saveCurrentProject() {
519560
static_cast<int>(workspace.batchResult->results.size()) - 1);
520561
workspace.result = workspace.batchResult->results[static_cast<std::size_t>(index)];
521562
}
522-
} else if (auto* resultWidget = visibleChild<ScenarioResultWidget>(centralWidget())) {
563+
} else if (activeWorkflowWidget == resultWidget) {
523564
workspace.activeView = ProjectWorkspaceView::ScenarioResult;
524565
if (resultWidget->returnAuthoringState().has_value()) {
525566
workspace.authoring = savedStateFromInitial(*resultWidget->returnAuthoringState());
@@ -529,8 +570,9 @@ void MainWindow::saveCurrentProject() {
529570
.frame = resultWidget->frame(),
530571
.risk = resultWidget->risk(),
531572
.artifacts = resultWidget->artifacts(),
573+
.navigationView = resultWidget->currentSavedNavigationView(),
532574
};
533-
} else if (auto* runWidget = visibleChild<ScenarioRunWidget>(centralWidget())) {
575+
} else if (activeWorkflowWidget == runWidget) {
534576
if (runWidget->returnAuthoringState().has_value()) {
535577
workspace.authoring = savedStateFromInitial(*runWidget->returnAuthoringState());
536578
}
@@ -732,6 +774,7 @@ void MainWindow::showScenarioResult(
732774
const safecrowd::domain::SimulationFrame& frame,
733775
const safecrowd::domain::ScenarioRiskSnapshot& risk,
734776
const safecrowd::domain::ScenarioResultArtifacts& artifacts,
777+
SavedResultNavigationView savedNavigationView,
735778
std::optional<ScenarioAuthoringWidget::InitialState> returnAuthoringState) {
736779
setCentralWidget(new ScenarioResultWidget(
737780
currentProject_.name,
@@ -755,6 +798,7 @@ void MainWindow::showScenarioResult(
755798
showLayoutReview(currentProject_);
756799
}
757800
},
801+
savedNavigationView,
758802
std::move(returnAuthoringState),
759803
this));
760804
}

src/application/MainWindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class MainWindow : public QMainWindow {
5858
const safecrowd::domain::SimulationFrame& frame,
5959
const safecrowd::domain::ScenarioRiskSnapshot& risk,
6060
const safecrowd::domain::ScenarioResultArtifacts& artifacts,
61+
SavedResultNavigationView savedNavigationView = SavedResultNavigationView::Bottleneck,
6162
std::optional<ScenarioAuthoringWidget::InitialState> returnAuthoringState = std::nullopt);
6263

6364
safecrowd::domain::SafeCrowdDomain& domain_;

src/application/ProjectPersistence.cpp

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,6 +1076,157 @@ safecrowd::domain::ScenarioRiskSnapshot riskSnapshotFromJson(const QJsonObject&
10761076
return risk;
10771077
}
10781078

1079+
QJsonObject densityCellMetricToJson(const safecrowd::domain::DensityCellMetric& cell) {
1080+
QJsonObject object;
1081+
object["center"] = pointArray(cell.center);
1082+
object["cellMin"] = pointArray(cell.cellMin);
1083+
object["cellMax"] = pointArray(cell.cellMax);
1084+
object["floorId"] = QString::fromStdString(cell.floorId);
1085+
object["agentCount"] = static_cast<qint64>(cell.agentCount);
1086+
object["densityPeoplePerSquareMeter"] = cell.densityPeoplePerSquareMeter;
1087+
return object;
1088+
}
1089+
1090+
safecrowd::domain::DensityCellMetric densityCellMetricFromJson(const QJsonObject& object) {
1091+
return {
1092+
.center = pointFromJson(object.value("center")),
1093+
.cellMin = pointFromJson(object.value("cellMin")),
1094+
.cellMax = pointFromJson(object.value("cellMax")),
1095+
.floorId = object.value("floorId").toString().toStdString(),
1096+
.agentCount = static_cast<std::size_t>(object.value("agentCount").toInteger()),
1097+
.densityPeoplePerSquareMeter = object.value("densityPeoplePerSquareMeter").toDouble(),
1098+
};
1099+
}
1100+
1101+
QJsonObject densityFieldSnapshotToJson(const safecrowd::domain::DensityFieldSnapshot& snapshot) {
1102+
QJsonObject object;
1103+
object["timeSeconds"] = snapshot.timeSeconds;
1104+
object["cellSizeMeters"] = snapshot.cellSizeMeters;
1105+
QJsonArray cells;
1106+
for (const auto& cell : snapshot.cells) {
1107+
cells.append(densityCellMetricToJson(cell));
1108+
}
1109+
object["cells"] = cells;
1110+
return object;
1111+
}
1112+
1113+
safecrowd::domain::DensityFieldSnapshot densityFieldSnapshotFromJson(const QJsonObject& object) {
1114+
safecrowd::domain::DensityFieldSnapshot snapshot;
1115+
snapshot.timeSeconds = object.value("timeSeconds").toDouble();
1116+
snapshot.cellSizeMeters = object.value("cellSizeMeters").toDouble();
1117+
for (const auto& value : object.value("cells").toArray()) {
1118+
snapshot.cells.push_back(densityCellMetricFromJson(value.toObject()));
1119+
}
1120+
return snapshot;
1121+
}
1122+
1123+
QJsonObject densitySummaryToJson(const safecrowd::domain::DensitySummary& summary) {
1124+
QJsonObject object;
1125+
object["cellSizeMeters"] = summary.cellSizeMeters;
1126+
object["highDensityThresholdPeoplePerSquareMeter"] = summary.highDensityThresholdPeoplePerSquareMeter;
1127+
object["peakDensityPeoplePerSquareMeter"] = summary.peakDensityPeoplePerSquareMeter;
1128+
object["peakAgentCount"] = static_cast<qint64>(summary.peakAgentCount);
1129+
object["peakAtSeconds"] = optionalDoubleToJson(summary.peakAtSeconds);
1130+
if (summary.peakCell.has_value()) {
1131+
object["peakCell"] = densityCellMetricToJson(*summary.peakCell);
1132+
}
1133+
object["highDensityDurationSeconds"] = summary.highDensityDurationSeconds;
1134+
QJsonArray peakCells;
1135+
for (const auto& cell : summary.peakCells) {
1136+
peakCells.append(densityCellMetricToJson(cell));
1137+
}
1138+
object["peakCells"] = peakCells;
1139+
object["peakField"] = densityFieldSnapshotToJson(summary.peakField);
1140+
return object;
1141+
}
1142+
1143+
safecrowd::domain::DensitySummary densitySummaryFromJson(const QJsonObject& object) {
1144+
safecrowd::domain::DensitySummary summary;
1145+
summary.cellSizeMeters = object.value("cellSizeMeters").toDouble();
1146+
summary.highDensityThresholdPeoplePerSquareMeter =
1147+
object.value("highDensityThresholdPeoplePerSquareMeter").toDouble(4.0);
1148+
summary.peakDensityPeoplePerSquareMeter = object.value("peakDensityPeoplePerSquareMeter").toDouble();
1149+
summary.peakAgentCount = static_cast<std::size_t>(object.value("peakAgentCount").toInteger());
1150+
summary.peakAtSeconds = optionalDoubleFromJson(object.value("peakAtSeconds"));
1151+
if (object.value("peakCell").isObject()) {
1152+
summary.peakCell = densityCellMetricFromJson(object.value("peakCell").toObject());
1153+
}
1154+
summary.highDensityDurationSeconds = object.value("highDensityDurationSeconds").toDouble();
1155+
for (const auto& value : object.value("peakCells").toArray()) {
1156+
summary.peakCells.push_back(densityCellMetricFromJson(value.toObject()));
1157+
}
1158+
if (object.value("peakField").isObject()) {
1159+
summary.peakField = densityFieldSnapshotFromJson(object.value("peakField").toObject());
1160+
}
1161+
return summary;
1162+
}
1163+
1164+
QJsonObject exitUsageMetricToJson(const safecrowd::domain::ExitUsageMetric& exit) {
1165+
QJsonObject object;
1166+
object["exitZoneId"] = QString::fromStdString(exit.exitZoneId);
1167+
object["exitLabel"] = QString::fromStdString(exit.exitLabel);
1168+
object["floorId"] = QString::fromStdString(exit.floorId);
1169+
object["evacuatedCount"] = static_cast<qint64>(exit.evacuatedCount);
1170+
object["usageRatio"] = exit.usageRatio;
1171+
object["lastExitTimeSeconds"] = optionalDoubleToJson(exit.lastExitTimeSeconds);
1172+
return object;
1173+
}
1174+
1175+
safecrowd::domain::ExitUsageMetric exitUsageMetricFromJson(const QJsonObject& object) {
1176+
safecrowd::domain::ExitUsageMetric exit;
1177+
exit.exitZoneId = object.value("exitZoneId").toString().toStdString();
1178+
exit.exitLabel = object.value("exitLabel").toString().toStdString();
1179+
exit.floorId = object.value("floorId").toString().toStdString();
1180+
exit.evacuatedCount = static_cast<std::size_t>(object.value("evacuatedCount").toInteger());
1181+
exit.usageRatio = object.value("usageRatio").toDouble();
1182+
exit.lastExitTimeSeconds = optionalDoubleFromJson(object.value("lastExitTimeSeconds"));
1183+
return exit;
1184+
}
1185+
1186+
QJsonObject zoneCompletionMetricToJson(const safecrowd::domain::ZoneCompletionMetric& zone) {
1187+
QJsonObject object;
1188+
object["zoneId"] = QString::fromStdString(zone.zoneId);
1189+
object["zoneLabel"] = QString::fromStdString(zone.zoneLabel);
1190+
object["floorId"] = QString::fromStdString(zone.floorId);
1191+
object["initialCount"] = static_cast<qint64>(zone.initialCount);
1192+
object["evacuatedCount"] = static_cast<qint64>(zone.evacuatedCount);
1193+
object["lastCompletionTimeSeconds"] = optionalDoubleToJson(zone.lastCompletionTimeSeconds);
1194+
return object;
1195+
}
1196+
1197+
safecrowd::domain::ZoneCompletionMetric zoneCompletionMetricFromJson(const QJsonObject& object) {
1198+
safecrowd::domain::ZoneCompletionMetric zone;
1199+
zone.zoneId = object.value("zoneId").toString().toStdString();
1200+
zone.zoneLabel = object.value("zoneLabel").toString().toStdString();
1201+
zone.floorId = object.value("floorId").toString().toStdString();
1202+
zone.initialCount = static_cast<std::size_t>(object.value("initialCount").toInteger());
1203+
zone.evacuatedCount = static_cast<std::size_t>(object.value("evacuatedCount").toInteger());
1204+
zone.lastCompletionTimeSeconds = optionalDoubleFromJson(object.value("lastCompletionTimeSeconds"));
1205+
return zone;
1206+
}
1207+
1208+
QJsonObject placementCompletionMetricToJson(const safecrowd::domain::PlacementCompletionMetric& placement) {
1209+
QJsonObject object;
1210+
object["placementId"] = QString::fromStdString(placement.placementId);
1211+
object["zoneId"] = QString::fromStdString(placement.zoneId);
1212+
object["floorId"] = QString::fromStdString(placement.floorId);
1213+
object["initialCount"] = static_cast<qint64>(placement.initialCount);
1214+
object["evacuatedCount"] = static_cast<qint64>(placement.evacuatedCount);
1215+
object["lastCompletionTimeSeconds"] = optionalDoubleToJson(placement.lastCompletionTimeSeconds);
1216+
return object;
1217+
}
1218+
1219+
safecrowd::domain::PlacementCompletionMetric placementCompletionMetricFromJson(const QJsonObject& object) {
1220+
safecrowd::domain::PlacementCompletionMetric placement;
1221+
placement.placementId = object.value("placementId").toString().toStdString();
1222+
placement.zoneId = object.value("zoneId").toString().toStdString();
1223+
placement.floorId = object.value("floorId").toString().toStdString();
1224+
placement.initialCount = static_cast<std::size_t>(object.value("initialCount").toInteger());
1225+
placement.evacuatedCount = static_cast<std::size_t>(object.value("evacuatedCount").toInteger());
1226+
placement.lastCompletionTimeSeconds = optionalDoubleFromJson(object.value("lastCompletionTimeSeconds"));
1227+
return placement;
1228+
}
1229+
10791230
QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifacts& artifacts) {
10801231
QJsonObject object;
10811232
QJsonArray progress;
@@ -1100,13 +1251,36 @@ QJsonObject resultArtifactsToJson(const safecrowd::domain::ScenarioResultArtifac
11001251
timing["t90Seconds"] = optionalDoubleToJson(artifacts.timingSummary.t90Seconds);
11011252
timing["t95Seconds"] = optionalDoubleToJson(artifacts.timingSummary.t95Seconds);
11021253
timing["finalEvacuationTimeSeconds"] = optionalDoubleToJson(artifacts.timingSummary.finalEvacuationTimeSeconds);
1254+
timing["targetTimeSeconds"] = artifacts.timingSummary.targetTimeSeconds;
1255+
timing["marginSeconds"] = optionalDoubleToJson(artifacts.timingSummary.marginSeconds);
11031256
if (artifacts.timingSummary.t90Frame.has_value()) {
11041257
timing["t90Frame"] = simulationFrameToJson(*artifacts.timingSummary.t90Frame);
11051258
}
11061259
if (artifacts.timingSummary.t95Frame.has_value()) {
11071260
timing["t95Frame"] = simulationFrameToJson(*artifacts.timingSummary.t95Frame);
11081261
}
11091262
object["timingSummary"] = timing;
1263+
1264+
object["densitySummary"] = densitySummaryToJson(artifacts.densitySummary);
1265+
1266+
QJsonArray exitUsage;
1267+
for (const auto& exit : artifacts.exitUsage) {
1268+
exitUsage.append(exitUsageMetricToJson(exit));
1269+
}
1270+
object["exitUsage"] = exitUsage;
1271+
1272+
QJsonArray zoneCompletion;
1273+
for (const auto& zone : artifacts.zoneCompletion) {
1274+
zoneCompletion.append(zoneCompletionMetricToJson(zone));
1275+
}
1276+
object["zoneCompletion"] = zoneCompletion;
1277+
1278+
QJsonArray placementCompletion;
1279+
for (const auto& placement : artifacts.placementCompletion) {
1280+
placementCompletion.append(placementCompletionMetricToJson(placement));
1281+
}
1282+
object["placementCompletion"] = placementCompletion;
1283+
11101284
return object;
11111285
}
11121286

@@ -1130,12 +1304,28 @@ safecrowd::domain::ScenarioResultArtifacts resultArtifactsFromJson(const QJsonOb
11301304
artifacts.timingSummary.t90Seconds = optionalDoubleFromJson(timing.value("t90Seconds"));
11311305
artifacts.timingSummary.t95Seconds = optionalDoubleFromJson(timing.value("t95Seconds"));
11321306
artifacts.timingSummary.finalEvacuationTimeSeconds = optionalDoubleFromJson(timing.value("finalEvacuationTimeSeconds"));
1307+
artifacts.timingSummary.targetTimeSeconds = timing.value("targetTimeSeconds").toDouble();
1308+
artifacts.timingSummary.marginSeconds = optionalDoubleFromJson(timing.value("marginSeconds"));
11331309
if (timing.value("t90Frame").isObject()) {
11341310
artifacts.timingSummary.t90Frame = simulationFrameFromJson(timing.value("t90Frame").toObject());
11351311
}
11361312
if (timing.value("t95Frame").isObject()) {
11371313
artifacts.timingSummary.t95Frame = simulationFrameFromJson(timing.value("t95Frame").toObject());
11381314
}
1315+
1316+
if (object.value("densitySummary").isObject()) {
1317+
artifacts.densitySummary = densitySummaryFromJson(object.value("densitySummary").toObject());
1318+
}
1319+
for (const auto& value : object.value("exitUsage").toArray()) {
1320+
artifacts.exitUsage.push_back(exitUsageMetricFromJson(value.toObject()));
1321+
}
1322+
for (const auto& value : object.value("zoneCompletion").toArray()) {
1323+
artifacts.zoneCompletion.push_back(zoneCompletionMetricFromJson(value.toObject()));
1324+
}
1325+
for (const auto& value : object.value("placementCompletion").toArray()) {
1326+
artifacts.placementCompletion.push_back(placementCompletionMetricFromJson(value.toObject()));
1327+
}
1328+
11391329
return artifacts;
11401330
}
11411331

@@ -1185,6 +1375,7 @@ QJsonObject resultStateToJson(const SavedScenarioResultState& result) {
11851375
object["frame"] = simulationFrameToJson(result.frame);
11861376
object["risk"] = riskSnapshotToJson(result.risk);
11871377
object["artifacts"] = resultArtifactsToJson(result.artifacts);
1378+
object["navigationView"] = static_cast<int>(result.navigationView);
11881379
return object;
11891380
}
11901381

@@ -1194,6 +1385,7 @@ SavedScenarioResultState resultStateFromJson(const QJsonObject& object) {
11941385
.frame = simulationFrameFromJson(object.value("frame").toObject()),
11951386
.risk = riskSnapshotFromJson(object.value("risk").toObject()),
11961387
.artifacts = resultArtifactsFromJson(object.value("artifacts").toObject()),
1388+
.navigationView = static_cast<SavedResultNavigationView>(object.value("navigationView").toInt()),
11971389
};
11981390
}
11991391

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 SavedScenarioBatchResultState {

0 commit comments

Comments
 (0)