make BuildingSystem stateless: FactoryState becomes a parameter

This commit is contained in:
2026-08-05 06:50:11 +02:00
parent d87d063b10
commit 114a43b205
15 changed files with 401 additions and 395 deletions

View File

@@ -56,7 +56,7 @@
struct Fixture
{
GameConfig cfg;
FactoryState state;
FactoryState state = makeFactoryState(cfg);
BeltSystem belts;
BuildingId nextBuildingId;
int stock;
@@ -79,7 +79,7 @@ struct Fixture
, nextBuildingId(1)
, stock(0)
, rng(42)
, buildings(cfg, state, belts,
, buildings(cfg, belts,
[this]() { return nextBuildingId++; },
[this](int n) { stock += n; },
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},
@@ -952,12 +952,12 @@ TEST_CASE("BehaviorSystem: full-cargo salvage ship moves toward SalvageBay", "[b
{
Fixture f;
const BuildingId bayId = f.buildings.place(BuildingType::SalvageBay,
const BuildingId bayId = f.buildings.place(f.state, BuildingType::SalvageBay,
QPoint(-4, 0), Rotation::East, 0).value();
Tick t = 0;
for (int i = 0; i < 500; ++i)
{
f.buildings.tickConstruction(t++);
f.buildings.tickConstruction(f.state, t++);
if (findBuilding(f.state, bayId) != nullptr)
{
break;
@@ -987,12 +987,12 @@ TEST_CASE("SalvagerSystem: full-cargo ship at its SalvageBay hands over cargo",
{
Fixture f;
const BuildingId bayId = f.buildings.place(BuildingType::SalvageBay,
const BuildingId bayId = f.buildings.place(f.state, BuildingType::SalvageBay,
QPoint(-4, 0), Rotation::East, 0).value();
Tick t = 0;
for (int i = 0; i < 500; ++i)
{
f.buildings.tickConstruction(t++);
f.buildings.tickConstruction(f.state, t++);
if (findBuilding(f.state, bayId) != nullptr) { break; }
}
const Building* bay = findBuilding(f.state, bayId);

View File

@@ -666,7 +666,7 @@ TEST_CASE("Blueprint placement: setRecipe on construction site stores recipe", "
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore");
const ConstructionSite* site = findSite(sim.getFactoryState(), id);
REQUIRE(site != nullptr);
@@ -680,7 +680,7 @@ TEST_CASE("Blueprint placement: recipe transfers to building after construction
const BuildingId id = SimulationTestAccess::place(sim,BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_copper_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_copper_ore");
// Miner construction_time_seconds = 10 → completesAt = secondsToTicks(10) = 300.
// Run 301 ticks (0..300) to process the completion tick.
@@ -726,7 +726,7 @@ TEST_CASE("Blueprint creation: a construction site's recipe is captured", "[blue
const BuildingId id =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(id != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore");
const Blueprint bp = captureBlueprintFromSelection(sim, { id });
@@ -743,7 +743,7 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site
const BuildingId idA =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
REQUIRE(idA != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idA, "mine_iron_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), idA, "mine_iron_ore");
for (int i = 0; i <= static_cast<int>(secondsToTicks(10.0)); ++i) { sim.tick(); }
REQUIRE(findBuilding(sim.getFactoryState(), idA) != nullptr);
@@ -751,7 +751,7 @@ TEST_CASE("Blueprint creation: mixed operational building and construction site
const BuildingId idB =
SimulationTestAccess::place(sim, BuildingType::Miner, QPoint(-6, 0), Rotation::East).value();
REQUIRE(idB != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(idB, "mine_copper_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), idB, "mine_copper_ore");
REQUIRE(findSite(sim.getFactoryState(), idB) != nullptr);
const Blueprint bp = captureBlueprintFromSelection(sim, { idA, idB });
@@ -854,7 +854,7 @@ TEST_CASE("Blueprint placement: setShipLayout on construction site stores layout
pm.rotation = Rotation::East;
layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(id, layout);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), id, layout);
const ConstructionSite* site = findSite(sim.getFactoryState(), id);
REQUIRE(site != nullptr);
@@ -878,7 +878,7 @@ TEST_CASE("Blueprint placement: ship layout transfers to building after construc
pm.rotation = Rotation::North;
layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(id, layout);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), id, layout);
// Shipyard construction_time_seconds = 30 in the test config.
double constructionTime = 0.0;

View File

@@ -37,8 +37,7 @@ BuildingId placeOperational(Simulation& sim, const GameConfig& cfg,
{
const BuildingDef* def = findDef(cfg, type);
REQUIRE(def != nullptr);
return SimulationTestAccess::buildings(sim).placeImmediate(
type, def->surfaceMask, anchor, Rotation::East);
return SimulationTestAccess::buildings(sim).placeImmediate(SimulationTestAccess::state(sim), type, def->surfaceMask, anchor, Rotation::East);
}
const ShipDef* findAvailableSchematic(const GameConfig& cfg)
@@ -67,7 +66,7 @@ TEST_CASE("readBuildingConfig returns a miner's selected recipe", "[copyconfig]"
Simulation sim(loadTestConfig(), 7);
const BuildingId id = placeOperational(sim, cfg, BuildingType::Miner, QPoint(0, 0));
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore");
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
REQUIRE(config.has_value());
@@ -103,8 +102,8 @@ TEST_CASE("readBuildingConfig returns a shipyard's schematic and layout",
REQUIRE(schematic != nullptr);
const BuildingId id = placeOperational(sim, cfg, BuildingType::Shipyard, QPoint(0, 0));
SimulationTestAccess::buildings(sim).setRecipe(id, schematic->id);
SimulationTestAccess::buildings(sim).setShipLayout(id, ShipLayoutConfig{});
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, schematic->id);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), id, ShipLayoutConfig{});
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
REQUIRE(config.has_value());
@@ -126,7 +125,7 @@ TEST_CASE("readBuildingConfig reads a queued construction site", "[copyconfig]")
REQUIRE(findBuilding(sim.getFactoryState(), id) == nullptr);
REQUIRE(findSite(sim.getFactoryState(), id) != nullptr);
SimulationTestAccess::buildings(sim).setRecipe(id, "mine_iron_ore");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), id, "mine_iron_ore");
const std::optional<BuildingConfig> config = readBuildingConfig(sim, id);
REQUIRE(config.has_value());

File diff suppressed because it is too large Load Diff

View File

@@ -52,7 +52,7 @@ static entt::entity findWeaponChild(EntityAdmin& admin, entt::entity ship)
struct CombatFixture
{
GameConfig cfg;
FactoryState state;
FactoryState state = makeFactoryState(cfg);
std::mt19937 rng;
EntityAdmin admin;
BuildingId nextBuildingId;
@@ -67,7 +67,7 @@ struct CombatFixture
, nextBuildingId(1)
, belts(cfg.world.beltSpeed_tps)
, ships(cfg, admin)
, buildings(cfg, state, belts,
, buildings(cfg, belts,
[this]() { return nextBuildingId++; },
[](int){},
[](const std::string&, QVector2D, const std::optional<ShipLayoutConfig>&) {},

View File

@@ -47,7 +47,7 @@ TEST_CASE("apply(PlaceBuildingCommand) with recipe matches place-then-setRecipe"
const BuildingId id =
SimulationTestAccess::place(viaDirect, BuildingType::Miner, QPoint(-2, 0), Rotation::East).value();
SimulationTestAccess::buildings(viaDirect).setRecipe(id, "mine_iron_ore");
SimulationTestAccess::buildings(viaDirect).setRecipe(SimulationTestAccess::state(viaDirect), id, "mine_iron_ore");
REQUIRE(viaCommand.computeStateChecksum() == viaDirect.computeStateChecksum());
}

View File

@@ -62,8 +62,7 @@ static const BuildingDef* findShipyardDef(const GameConfig& cfg)
static BuildingId placeShipyard(Simulation& sim, const BuildingDef& yardDef)
{
return SimulationTestAccess::buildings(sim).placeImmediate(
BuildingType::Shipyard,
return SimulationTestAccess::buildings(sim).placeImmediate(SimulationTestAccess::state(sim), BuildingType::Shipyard,
yardDef.surfaceMask,
QPoint(0, 0),
Rotation::East);
@@ -73,7 +72,7 @@ static void fillMaterials(Simulation& sim, BuildingId yardId,
const ShipDef& def,
const ShipLayoutConfig& layout)
{
SimulationTestAccess::buildings(sim).forEachBuilding([&](Building& b) {
SimulationTestAccess::buildings(sim).forEachBuilding(SimulationTestAccess::state(sim), [&](Building& b) {
if (b.id != yardId)
{
return;
@@ -208,7 +207,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId,"interceptor");
ShipLayoutConfig layout;
PlacedModule pm;
@@ -217,7 +216,7 @@ TEST_CASE("Shipyard: setShipLayout reinitializes buffers with module materials",
pm.rotation = Rotation::East;
layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), yardId, layout);
const Building* b = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b != nullptr);
@@ -237,7 +236,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId,"interceptor");
// Fill materials and tick to start production.
ShipLayoutConfig emptyLayout;
@@ -256,7 +255,7 @@ TEST_CASE("Shipyard: setShipLayout cancels in-progress production",
pm.rotation = Rotation::East;
layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), yardId, layout);
const Building* b2 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b2 != nullptr);
@@ -279,7 +278,7 @@ TEST_CASE("Shipyard: builds a bare hull when no layout is configured",
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId, "interceptor");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId, "interceptor");
// Deliberately no setShipLayout: recipe set, layout left unconfigured.
// Charge only the base-hull materials (an empty layout adds none).
@@ -314,7 +313,7 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId,"interceptor");
ShipLayoutConfig layout;
PlacedModule pm;
@@ -322,13 +321,13 @@ TEST_CASE("Shipyard: setRecipe clears ship layout", "[modules][shipyard]")
pm.position = QPoint(0, 0);
pm.rotation = Rotation::East;
layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), yardId, layout);
const Building* b1 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b1 != nullptr);
REQUIRE(b1->shipLayout.has_value());
SimulationTestAccess::buildings(sim).setRecipe(yardId,"destroyer");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId,"destroyer");
const Building* b2 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b2 != nullptr);
@@ -343,7 +342,7 @@ TEST_CASE("Shipyard: setRecipe with unchanged recipe keeps ship layout",
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId,"interceptor");
ShipLayoutConfig layout;
PlacedModule pm;
@@ -351,14 +350,14 @@ TEST_CASE("Shipyard: setRecipe with unchanged recipe keeps ship layout",
pm.position = QPoint(0, 0);
pm.rotation = Rotation::East;
layout.placedModules.push_back(pm);
SimulationTestAccess::buildings(sim).setShipLayout(yardId, layout);
SimulationTestAccess::buildings(sim).setShipLayout(SimulationTestAccess::state(sim), yardId, layout);
const Building* b1 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b1 != nullptr);
REQUIRE(b1->shipLayout.has_value());
// Re-selecting the same recipe must be a no-op and preserve the layout.
SimulationTestAccess::buildings(sim).setRecipe(yardId,"interceptor");
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId,"interceptor");
const Building* b2 = findBuilding(sim.getFactoryState(), yardId);
REQUIRE(b2 != nullptr);

View File

@@ -58,8 +58,7 @@ static const BuildingDef* findShipyardDef(const GameConfig& cfg)
static BuildingId placeShipyard(Simulation& sim, const BuildingDef& yardDef)
{
return SimulationTestAccess::buildings(sim).placeImmediate(
BuildingType::Shipyard,
return SimulationTestAccess::buildings(sim).placeImmediate(SimulationTestAccess::state(sim), BuildingType::Shipyard,
yardDef.surfaceMask,
QPoint(0, 0),
Rotation::East);
@@ -75,7 +74,7 @@ static int countShips(Simulation& sim)
static void fillMaterials(Simulation& sim, BuildingId yardId, const ShipDef& def)
{
SimulationTestAccess::buildings(sim).forEachBuilding([&](Building& b)
SimulationTestAccess::buildings(sim).forEachBuilding(SimulationTestAccess::state(sim), [&](Building& b)
{
if (b.id != yardId)
{
@@ -107,7 +106,7 @@ TEST_CASE("Shipyard: spawns a player ship after production cycle completes",
const BuildingId yardId = placeShipyard(sim, *yardDef);
REQUIRE(yardId != kInvalidBuildingId);
SimulationTestAccess::buildings(sim).setRecipe(yardId, def->id);
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId, def->id);
fillMaterials(sim, yardId, *def);
// First tick: materials consumed, production cycle starts — no ship yet.
@@ -166,7 +165,7 @@ TEST_CASE("Shipyard: does not spawn with insufficient materials", "[shipyard]")
const int shipsBefore = countShips(sim);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId, def->id);
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId, def->id);
// Materials remain at zero (default after setRecipe); no cycle starts.
const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);
@@ -188,7 +187,7 @@ TEST_CASE("Shipyard: spawns a second ship after materials replenished", "[shipya
REQUIRE(yardDef != nullptr);
const BuildingId yardId = placeShipyard(sim, *yardDef);
SimulationTestAccess::buildings(sim).setRecipe(yardId, def->id);
SimulationTestAccess::buildings(sim).setRecipe(SimulationTestAccess::state(sim), yardId, def->id);
const Tick cycleTicks = secondsToTicks(def->schematic.productionTimeSeconds);

View File

@@ -7,6 +7,7 @@
#include "BuildingId.h"
#include "BuildingType.h"
#include "Rotation.h"
#include "FactoryState.h"
#include "Simulation.h"
class BeltSystem;
@@ -25,6 +26,7 @@ class BuildingSystem;
struct SimulationTestAccess
{
static BuildingSystem& buildings(Simulation& sim) { return sim.getBuildingsMutable(); }
static FactoryState& state(Simulation& sim) { return sim.m_factoryState; }
static BeltSystem& belts(Simulation& sim) { return sim.getBeltsMutable(); }
static std::optional<BuildingId> place(Simulation& sim, BuildingType type,