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}/VisualsLoader.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h
|
${CMAKE_CURRENT_SOURCE_DIR}/MainWindow.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ModalDimOverlay.h
|
${CMAKE_CURRENT_SOURCE_DIR}/ModalDimOverlay.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ModalPauseScope.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.h
|
${CMAKE_CURRENT_SOURCE_DIR}/GameWorldView.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
${CMAKE_CURRENT_SOURCE_DIR}/HeaderBar.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
${CMAKE_CURRENT_SOURCE_DIR}/BuildButtonGrid.h
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "ShipLayoutBlueprintSerializer.h"
|
#include "ShipLayoutBlueprintSerializer.h"
|
||||||
#include "ShipLayoutDialog.h"
|
#include "ShipLayoutDialog.h"
|
||||||
#include "ItemIconCache.h"
|
#include "ItemIconCache.h"
|
||||||
|
#include "ModalPauseScope.h"
|
||||||
#include "Simulation.h"
|
#include "Simulation.h"
|
||||||
#include "Tick.h"
|
#include "Tick.h"
|
||||||
#include "VisualsLoader.h"
|
#include "VisualsLoader.h"
|
||||||
@@ -167,8 +168,7 @@ void MainWindow::layoutPanels()
|
|||||||
|
|
||||||
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
|
void MainWindow::handleEvent(std::shared_ptr<const SchematicChoicesAvailableEvent> event)
|
||||||
{
|
{
|
||||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
ModalPauseScope pause(*m_gameWorldView);
|
||||||
m_gameWorldView->setGameSpeed(0.0);
|
|
||||||
|
|
||||||
ModalDimScope dim(*m_dimOverlay);
|
ModalDimScope dim(*m_dimOverlay);
|
||||||
SchematicChoiceDialog dialog(event->choices, m_sim->getConfig().recipes, this);
|
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();
|
command->choiceIndex = dialog.getChosenIndex();
|
||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
std::make_shared<CommandRequestedEvent>(command));
|
std::make_shared<CommandRequestedEvent>(command));
|
||||||
|
|
||||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
|
||||||
m_gameWorldView->resetFrameTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*event*/)
|
void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*event*/)
|
||||||
{
|
{
|
||||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
ModalPauseScope pause(*m_gameWorldView);
|
||||||
m_gameWorldView->setGameSpeed(0.0);
|
|
||||||
|
|
||||||
ModalDimScope dim(*m_dimOverlay);
|
ModalDimScope dim(*m_dimOverlay);
|
||||||
QMessageBox box(this);
|
QMessageBox box(this);
|
||||||
@@ -204,12 +200,13 @@ void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*e
|
|||||||
std::optional<GameConfig> newConfig = reloadConfig();
|
std::optional<GameConfig> newConfig = reloadConfig();
|
||||||
if (!newConfig.has_value())
|
if (!newConfig.has_value())
|
||||||
{
|
{
|
||||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
|
||||||
m_gameWorldView->resetFrameTimer();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Restart is a command boundary; the view resets when the drain applies
|
// Restart is a command boundary; the view resets when the drain applies
|
||||||
// it (see GameWorldView::onFrame). A fresh random seed starts a new run.
|
// 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>();
|
std::shared_ptr<ResetCommand> command = std::make_shared<ResetCommand>();
|
||||||
command->config = std::make_shared<GameConfig>(std::move(*newConfig));
|
command->config = std::make_shared<GameConfig>(std::move(*newConfig));
|
||||||
command->seed = std::random_device{}();
|
command->seed = std::random_device{}();
|
||||||
@@ -218,13 +215,9 @@ void MainWindow::handleEvent(std::shared_ptr<const EscapeMenuRequestedEvent> /*e
|
|||||||
}
|
}
|
||||||
else if (clicked == quitBtn)
|
else if (clicked == quitBtn)
|
||||||
{
|
{
|
||||||
|
pause.release();
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
|
||||||
m_gameWorldView->resetFrameTimer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<GameConfig> MainWindow::reloadConfig()
|
std::optional<GameConfig> MainWindow::reloadConfig()
|
||||||
@@ -252,8 +245,7 @@ void MainWindow::openShipLayoutDialog(BuildingId shipyardId,
|
|||||||
const std::string& schematicId,
|
const std::string& schematicId,
|
||||||
const ShipLayoutConfig& currentLayout)
|
const ShipLayoutConfig& currentLayout)
|
||||||
{
|
{
|
||||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
ModalPauseScope pause(*m_gameWorldView);
|
||||||
m_gameWorldView->setGameSpeed(0.0);
|
|
||||||
|
|
||||||
std::set<std::string> unlockedModuleIds;
|
std::set<std::string> unlockedModuleIds;
|
||||||
for (const ModuleDef& def : m_sim->getConfig().modules.modules)
|
for (const ModuleDef& def : m_sim->getConfig().modules.modules)
|
||||||
@@ -279,9 +271,6 @@ void MainWindow::openShipLayoutDialog(BuildingId shipyardId,
|
|||||||
EventManager::getInstance()->sendEventImmediately(
|
EventManager::getInstance()->sendEventImmediately(
|
||||||
std::make_shared<CommandRequestedEvent>(command));
|
std::make_shared<CommandRequestedEvent>(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
|
||||||
m_gameWorldView->resetFrameTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::handleEvent(std::shared_ptr<const LayoutDialogRequestedEvent> event)
|
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)
|
void MainWindow::handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event)
|
||||||
{
|
{
|
||||||
const double prevSpeed = m_gameWorldView->getGameSpeed();
|
ModalPauseScope pause(*m_gameWorldView);
|
||||||
m_gameWorldView->setGameSpeed(0.0);
|
|
||||||
|
|
||||||
// A construction site has no Building yet; fall back to its site record so
|
// 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).
|
// 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);
|
b ? nullptr : m_sim->getBuildings().findSite(event->buildingId);
|
||||||
if (!b && !s)
|
if (!b && !s)
|
||||||
{
|
{
|
||||||
m_gameWorldView->setGameSpeed(prevSpeed);
|
|
||||||
m_gameWorldView->resetFrameTimer();
|
|
||||||
return;
|
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 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
|
// 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
|
// 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)
|
if (autoOpenLayout)
|
||||||
{
|
{
|
||||||
openShipLayoutDialog(event->buildingId, chosenSchematic, ShipLayoutConfig{});
|
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