Refresh selected-building panel when paused player commands drain

Option A made the per-tick refresh authoritative for the shipyard layout
preview and Configure Layout button, but that path is driven by
TickAdvancedEvent, which only fires while the game is running. Choosing a
schematic while paused therefore still left the widgets hidden until the
game was unpaused or the building re-selected, because the queued
SetRecipeCommand drains on the next frame but no tick advances.

Emit a new PlayerCommandsAppliedEvent from GameWorldView::onFrame once per
frame when queued commands were drained, and have SelectedBuildingPanel
refresh its selection display in response. This is a presentation-only
notification: it is emitted only on the live path (not during replay
playback), touches neither the command queue nor the simulation, and its
only handler never enqueues commands -- so replay recording and
determinism are unaffected.

Factor the former TickAdvancedEvent handler body into
refreshSelectionDisplay() and call it from both handlers to avoid
duplication.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DyCu8vwChKMbLJQ3xosYEN
This commit is contained in:
2026-07-08 19:31:29 +02:00
parent 1469178296
commit 806cc937d6
5 changed files with 47 additions and 0 deletions

View File

@@ -30,6 +30,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/BeamFiredEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/DebugDrawToggledEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/CommandRequestedEvent.h
${CMAKE_CURRENT_SOURCE_DIR}/PlayerCommandsAppliedEvent.h
PARENT_SCOPE
)

View File

@@ -0,0 +1,13 @@
#pragma once
#include "Event.h"
// Emitted by GameWorldView once per frame after queued player commands have been
// drained and applied to the simulation. It lets presentation widgets refresh
// even while the game is paused (no tick advances, so no TickAdvancedEvent), for
// example so a shipyard's layout preview appears immediately after its schematic
// is chosen. It is a UI notification only and never feeds back into the command
// queue, so it has no effect on replay recording or determinism.
class PlayerCommandsAppliedEvent : public Event
{
};

View File

@@ -61,6 +61,7 @@
#include "BuildingBlocksChangedEvent.h"
#include "GameSpeedChangedEvent.h"
#include "SchematicChoicesAvailableEvent.h"
#include "PlayerCommandsAppliedEvent.h"
#include "TickAdvancedEvent.h"
namespace
@@ -211,6 +212,7 @@ void GameWorldView::onFrame()
// Drain queued player commands once per frame, before the tick batch. This
// runs even at 0x so a paused player sees placed construction sites
// immediately, while staying deterministic (see docs/replay_design.md).
const bool commandsApplied = m_commandManager.hasPending();
m_commandManager.drain();
// A drained Reset reinitialized the simulation; reset the view to match.
@@ -220,6 +222,17 @@ void GameWorldView::onFrame()
resetForNewGame();
}
// Notify presentation widgets that queued commands were applied, so a
// paused player still sees the effect (e.g. a shipyard's layout preview
// after picking a schematic) even though no tick advances. UI-only: this
// does not touch the command queue or simulation, so replay recording
// and determinism are unaffected.
if (commandsApplied)
{
EventManager::getInstance()->sendEventImmediately(
std::make_shared<PlayerCommandsAppliedEvent>());
}
const int ticks = m_tickDriver.advance(
static_cast<double>(elapsed), m_gameSpeedMultiplier);
for (int i = 0; i < ticks; ++i)

View File

@@ -33,6 +33,7 @@
#include "ItemType.h"
#include "LayoutDialogRequestedEvent.h"
#include "ModulesConfig.h"
#include "PlayerCommandsAppliedEvent.h"
#include "RecipeSelectionDialog.h"
#include "RecipeSelectionRequestedEvent.h"
#include "Rotation.h"
@@ -560,6 +561,21 @@ const ShipDef* SelectedBuildingPanel::findShipDef(const std::string& id) const
}
void SelectedBuildingPanel::handleEvent(std::shared_ptr<const TickAdvancedEvent> /*event*/)
{
refreshSelectionDisplay();
}
void SelectedBuildingPanel::handleEvent(
std::shared_ptr<const PlayerCommandsAppliedEvent> /*event*/)
{
// Player commands (e.g. choosing a shipyard schematic) are applied by a
// queued drain, not synchronously. When the game is paused no tick advances,
// so TickAdvancedEvent never fires; refresh here too, otherwise the panel
// would not reflect the change until the next tick or a re-selection.
refreshSelectionDisplay();
}
void SelectedBuildingPanel::refreshSelectionDisplay()
{
if (m_selectedEntity.has_value())
{

View File

@@ -16,6 +16,7 @@
#include "EntitySelectedEvent.h"
#include "EventHandler.h"
#include "GameConfig.h"
#include "PlayerCommandsAppliedEvent.h"
#include "RecipesConfig.h"
#include "SelectionChangedEvent.h"
#include "ShipLayout.h"
@@ -33,6 +34,7 @@ class QVBoxLayout;
class SelectedBuildingPanel : public QWidget,
public CombinedEventHandler<TickAdvancedEvent,
PlayerCommandsAppliedEvent,
EntitySelectedEvent,
SelectionChangedEvent,
DebugDrawToggledEvent>
@@ -46,6 +48,7 @@ public:
private:
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
void handleEvent(std::shared_ptr<const PlayerCommandsAppliedEvent> event) override;
void handleEvent(std::shared_ptr<const EntitySelectedEvent> event) override;
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const DebugDrawToggledEvent> event) override;
@@ -57,6 +60,7 @@ private slots:
private:
void onSelectionChanged(const std::vector<BuildingId>& ids);
void refreshSelectionDisplay();
void rebuild();
void hideAllWidgets();
void clearContent();