add ModalPauseScope for the pause-around-modal idiom
This commit is contained in:
@@ -4,6 +4,7 @@ SET(HDRS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/VisualsLoader.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ModalDimOverlay.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ModalPauseScope.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "ShipLayoutBlueprintSerializer.h"
|
||||
#include "ShipLayoutDialog.h"
|
||||
#include "ItemIconCache.h"
|
||||
#include "ModalPauseScope.h"
|
||||
#include "Simulation.h"
|
||||
#include "Tick.h"
|
||||
#include "VisualsLoader.h"
|
||||
@@ -167,8 +168,7 @@ void MainWindow::layoutPanels()
|
||||
|
||||
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
|
||||
{
|
||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
||||
m_gameWorldView->setGameSpeed(0.0);
|
||||
ModalPauseScope pause(*m_gameWorldView);
|
||||
|
||||
ModalDimScope dim(*m_dimOverlay);
|
||||
SchematicChoiceDialog dialog(event->choices, m_sim->getConfig().recipes, this);
|
||||
@@ -179,15 +179,11 @@ void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEven
|
||||
command->choiceIndex = dialog.getChosenIndex();
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<CommandRequestedEvent>(command));
|
||||
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
}
|
||||
|
||||
void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*event*/)
|
||||
{
|
||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
||||
m_gameWorldView->setGameSpeed(0.0);
|
||||
ModalPauseScope pause(*m_gameWorldView);
|
||||
|
||||
ModalDimScope dim(*m_dimOverlay);
|
||||
QMessageBox box(this);
|
||||
@@ -204,12 +200,13 @@ void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*e
|
||||
std::optional<GameConfig> newConfig = reloadConfig();
|
||||
if (!newConfig.has_value())
|
||||
{
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
return;
|
||||
}
|
||||
// Restart is a command boundary; the view resets when the drain applies
|
||||
// it (see GameWorldView::onFrame). A fresh random seed starts a new run.
|
||||
// resetForNewGame() sets the speed for the new run, so the pre-restart
|
||||
// speed is deliberately not restored here.
|
||||
pause.release();
|
||||
std::shared_ptr<ResetCommand> command = std::make_shared<ResetCommand>();
|
||||
command->config = std::make_shared<GameConfig>(std::move(*newConfig));
|
||||
command->seed = std::random_device{}();
|
||||
@@ -218,13 +215,9 @@ void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*e
|
||||
}
|
||||
else if (clicked == quitBtn)
|
||||
{
|
||||
pause.release();
|
||||
close();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<GameConfig> MainWindow::reloadConfig()
|
||||
@@ -252,8 +245,7 @@ void MainWindow::openShipLayoutDialog(BuildingId shipyardId,
|
||||
const std::string& schematicId,
|
||||
const ShipLayoutConfig& currentLayout)
|
||||
{
|
||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
||||
m_gameWorldView->setGameSpeed(0.0);
|
||||
ModalPauseScope pause(*m_gameWorldView);
|
||||
|
||||
std::set<std::string> unlockedModuleIds;
|
||||
for (const ModuleDef& def : m_sim->getConfig().modules.modules)
|
||||
@@ -279,9 +271,6 @@ void MainWindow::openShipLayoutDialog(BuildingId shipyardId,
|
||||
EventManager::getInstance()->sendEventImmediately(
|
||||
std::make_shared<CommandRequestedEvent>(command));
|
||||
}
|
||||
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
}
|
||||
|
||||
void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event)
|
||||
@@ -311,8 +300,7 @@ void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> e
|
||||
|
||||
void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event)
|
||||
{
|
||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
||||
m_gameWorldView->setGameSpeed(0.0);
|
||||
ModalPauseScope pause(*m_gameWorldView);
|
||||
|
||||
// A construction site has no Building yet; fall back to its site record so
|
||||
// the recipe/schematic can be chosen before it is built (REQ-BLD-SITE-CONFIG).
|
||||
@@ -321,8 +309,6 @@ void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent
|
||||
b ? nullptr : m_sim->getBuildings().findSite(event->buildingId);
|
||||
if (!b && !s)
|
||||
{
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -361,13 +347,11 @@ void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent
|
||||
}
|
||||
}
|
||||
|
||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
||||
m_gameWorldView->resetFrameTimer();
|
||||
|
||||
// The SetRecipeCommand above is queued (drains on a later frame) and clears
|
||||
// the shipyard's layout, so open the dialog with the chosen schematic and an
|
||||
// empty layout rather than reading the not-yet-updated building state. Speed
|
||||
// is already restored so the helper snapshots the real speed to restore.
|
||||
// is restored first so the helper snapshots the real speed to restore.
|
||||
pause.restore();
|
||||
if (autoOpenLayout)
|
||||
{
|
||||
openShipLayoutDialog(event->buildingId, chosenSchematic, ShipLayoutConfig{});
|
||||
|
||||
55
src/ui/ModalPauseScope.h
Normal file
55
src/ui/ModalPauseScope.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "GameWorldView.h"
|
||||
|
||||
// RAII guard for the "pause the game while a modal is open" idiom (REQ-UI-SPEED).
|
||||
// Constructing it snapshots the current game speed and pauses the game; on scope
|
||||
// exit it restores the snapshotted speed and rebases the render frame timer, so the
|
||||
// wall time the player spent in the dialog is not converted into simulation ticks.
|
||||
//
|
||||
// Pairs with ModalDimScope, which the same call sites use for the dim overlay.
|
||||
//
|
||||
// Two escape hatches for the paths that must not simply restore at scope exit:
|
||||
// restore() — restore now instead of at scope exit, for when more work has to run
|
||||
// at the player's real speed before the scope ends (e.g. a follow-up
|
||||
// dialog that snapshots the speed itself).
|
||||
// release() — abandon the restore entirely, for when the game is about to be
|
||||
// reset or the window closed and the old speed is meaningless.
|
||||
// Both are idempotent and the destructor does nothing once either has run.
|
||||
class ModalPauseScope
|
||||
{
|
||||
public:
|
||||
explicit ModalPauseScope(GameWorldView& view)
|
||||
: m_view(view)
|
||||
, m_previousGameSpeed(view.getGameSpeed())
|
||||
, m_restorePending(true)
|
||||
{
|
||||
m_view.setGameSpeed(0.0);
|
||||
}
|
||||
|
||||
~ModalPauseScope()
|
||||
{
|
||||
restore();
|
||||
}
|
||||
|
||||
void restore()
|
||||
{
|
||||
if (!m_restorePending) { return; }
|
||||
m_restorePending = false;
|
||||
m_view.setGameSpeed(m_previousGameSpeed);
|
||||
m_view.resetFrameTimer();
|
||||
}
|
||||
|
||||
void release()
|
||||
{
|
||||
m_restorePending = false;
|
||||
}
|
||||
|
||||
ModalPauseScope(const ModalPauseScope&) = delete;
|
||||
ModalPauseScope& operator=(const ModalPauseScope&) = delete;
|
||||
|
||||
private:
|
||||
GameWorldView& m_view;
|
||||
double m_previousGameSpeed;
|
||||
bool m_restorePending;
|
||||
};
|
||||
Reference in New Issue
Block a user