gather the factory's world data into FactoryState

BuildingSystem is the one system in the codebase that owns the world data it
operates on. The ecs/system/ classes already do the opposite — AiSystem and
SalvagerSystem hold config and their own scratch, and take EntityAdmin and the
other systems as tick arguments — so this is bringing the outlier in line, not
inventing a pattern.

Phase 1 of that: the buildings vector, both work queues and the tile grid move
into a FactoryState struct, still owned by BuildingSystem. Every method reaches
through m_state. The public API is untouched, so none of the 33 files that
reference BuildingSystem needed a change.

DeconstructionEntry moves out of BuildingSystem's private section into
FactoryState.h, since the queue that holds it lives there now.

m_asteroidWidth_tiles stays on the system: it is not checksummed and is a cached
placement bound derived from config and the expansion count, not factory data.

The intent is for Simulation to own FactoryState and pass it into the tick
methods, leaving the systems stateless over it. FactoryState.h notes explicitly
that this is a data/behaviour split and not a step toward putting buildings in
the entity model, which architecture.md rules out.

Verified with a golden-checksum capture before and after — all four sample
ticks identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
This commit is contained in:
2026-08-04 20:39:34 +02:00
parent 71d0dad3f2
commit e39b81eb22
4 changed files with 128 additions and 97 deletions

View File

@@ -308,7 +308,7 @@ std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor
for (const QPoint& cell : mask.bodyCells) for (const QPoint& cell : mask.bodyCells)
{ {
const QPoint absCell = anchor + cell; const QPoint absCell = anchor + cell;
m_grid.occupy(absCell, id); m_state.grid.occupy(absCell, id);
} }
// Build construction site. // Build construction site.
@@ -323,13 +323,13 @@ std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor
site.bodyCells.push_back(anchor + cell); site.bodyCells.push_back(anchor + cell);
} }
if (m_constructionQueue.empty()) if (m_state.constructionQueue.empty())
{ {
site.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds); site.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds);
} }
// else: completesAt remains 0 (queued, not yet started). // else: completesAt remains 0 (queued, not yet started).
m_constructionQueue.push_back(std::move(site)); m_state.constructionQueue.push_back(std::move(site));
return id; return id;
} }
@@ -405,15 +405,15 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
{ {
// Construction site? Removed instantly with the full refund; never queued // Construction site? Removed instantly with the full refund; never queued
// for deconstruction (REQ-BLD-DECONSTRUCT). // for deconstruction (REQ-BLD-DECONSTRUCT).
for (std::deque<ConstructionSite>::iterator it = m_constructionQueue.begin(); for (std::deque<ConstructionSite>::iterator it = m_state.constructionQueue.begin();
it != m_constructionQueue.end(); it != m_state.constructionQueue.end();
++it) ++it)
{ {
if (it->id == id) if (it->id == id)
{ {
const BuildingDef* def = m_config.buildings.findBuildingDef(it->type); const BuildingDef* def = m_config.buildings.findBuildingDef(it->type);
m_grid.release(it->bodyCells); m_state.grid.release(it->bodyCells);
m_constructionQueue.erase(it); m_state.constructionQueue.erase(it);
if (def) if (def)
{ {
return def->cost; return def->cost;
@@ -425,7 +425,7 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
// Operational building? Append it to the deconstruction queue rather than // Operational building? Append it to the deconstruction queue rather than
// removing it now; the partial refund is credited on completion in // removing it now; the partial refund is credited on completion in
// tickDeconstruction (REQ-BLD-DECON-QUEUE). // tickDeconstruction (REQ-BLD-DECON-QUEUE).
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
if (building.id != id) { continue; } if (building.id != id) { continue; }
if (building.queuedForDeconstruction) { return 0; } // already queued if (building.queuedForDeconstruction) { return 0; } // already queued
@@ -453,8 +453,8 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
m_belts.removeTile(building.anchor); m_belts.removeTile(building.anchor);
} }
const bool wasEmpty = m_deconstructionQueue.empty(); const bool wasEmpty = m_state.deconstructionQueue.empty();
m_deconstructionQueue.push_back(std::move(entry)); m_state.deconstructionQueue.push_back(std::move(entry));
if (wasEmpty) if (wasEmpty)
{ {
startFrontDeconstruction(currentTick); startFrontDeconstruction(currentTick);
@@ -467,8 +467,8 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick)
void BuildingSystem::startFrontDeconstruction(Tick currentTick) void BuildingSystem::startFrontDeconstruction(Tick currentTick)
{ {
if (m_deconstructionQueue.empty()) { return; } if (m_state.deconstructionQueue.empty()) { return; }
DeconstructionEntry& front = m_deconstructionQueue.front(); DeconstructionEntry& front = m_state.deconstructionQueue.front();
if (front.completesAt == 0) if (front.completesAt == 0)
{ {
front.completesAt = front.completesAt =
@@ -483,7 +483,7 @@ void BuildingSystem::startFrontDeconstruction(Tick currentTick)
void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId) void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
{ {
// Construction site: store recipe for when building completes. // Construction site: store recipe for when building completes.
for (ConstructionSite& site : m_constructionQueue) for (ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id == id) if (site.id == id)
{ {
@@ -506,7 +506,7 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
} }
// Operational building: clear buffers and re-init. // Operational building: clear buffers and re-init.
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
if (building.id == id) if (building.id == id)
{ {
@@ -558,7 +558,7 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId)
void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout) void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout)
{ {
for (ConstructionSite& site : m_constructionQueue) for (ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id == id) if (site.id == id)
{ {
@@ -567,7 +567,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
} }
} }
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
if (building.id == id) if (building.id == id)
{ {
@@ -594,7 +594,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout
std::optional<BeltSystem::SplitterInfo> std::optional<BeltSystem::SplitterInfo>
BuildingSystem::getSiteSplitterInfo(BuildingId id) const BuildingSystem::getSiteSplitterInfo(BuildingId id) const
{ {
for (const ConstructionSite& site : m_constructionQueue) for (const ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id != id) { continue; } if (site.id != id) { continue; }
if (site.type != BuildingType::Splitter) { return std::nullopt; } if (site.type != BuildingType::Splitter) { return std::nullopt; }
@@ -618,7 +618,7 @@ void BuildingSystem::setSiteSplitterFilters(BuildingId id,
const std::vector<ItemType>& filterA, const std::vector<ItemType>& filterA,
const std::vector<ItemType>& filterB) const std::vector<ItemType>& filterB)
{ {
for (ConstructionSite& site : m_constructionQueue) for (ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id == id && site.type == BuildingType::Splitter) if (site.id == id && site.type == BuildingType::Splitter)
{ {
@@ -636,12 +636,12 @@ void BuildingSystem::setSiteSplitterFilters(BuildingId id,
void BuildingSystem::tickConstruction(Tick currentTick) void BuildingSystem::tickConstruction(Tick currentTick)
{ {
TRACE(); TRACE();
if (m_constructionQueue.empty()) if (m_state.constructionQueue.empty())
{ {
return; return;
} }
ConstructionSite& front = m_constructionQueue.front(); ConstructionSite& front = m_state.constructionQueue.front();
// Guard: if somehow the front site was never started, start it now. // Guard: if somehow the front site was never started, start it now.
if (front.completesAt == 0) if (front.completesAt == 0)
@@ -719,18 +719,18 @@ void BuildingSystem::tickConstruction(Tick currentTick)
// filters configured while under construction carry over (REQ-BLD-SITE-CONFIG). // filters configured while under construction carry over (REQ-BLD-SITE-CONFIG).
reregisterBeltTile(building, front.splitterFilterA, front.splitterFilterB); reregisterBeltTile(building, front.splitterFilterA, front.splitterFilterB);
m_buildings.push_back(std::move(building)); m_state.buildings.push_back(std::move(building));
m_constructionQueue.pop_front(); m_state.constructionQueue.pop_front();
// Start next queued site if present. // Start next queued site if present.
if (!m_constructionQueue.empty() && m_constructionQueue.front().completesAt == 0) if (!m_state.constructionQueue.empty() && m_state.constructionQueue.front().completesAt == 0)
{ {
const BuildingDef* nextDef = const BuildingDef* nextDef =
m_config.buildings.findBuildingDef(m_constructionQueue.front().type); m_config.buildings.findBuildingDef(m_state.constructionQueue.front().type);
if (nextDef) if (nextDef)
{ {
m_constructionQueue.front().completesAt = m_state.constructionQueue.front().completesAt =
currentTick + secondsToTicks(nextDef->constructionTimeSeconds); currentTick + secondsToTicks(nextDef->constructionTimeSeconds);
} }
} }
@@ -767,12 +767,12 @@ void BuildingSystem::reregisterBeltTile(const Building& building,
void BuildingSystem::tickDeconstruction(Tick currentTick) void BuildingSystem::tickDeconstruction(Tick currentTick)
{ {
TRACE(); TRACE();
if (m_deconstructionQueue.empty()) if (m_state.deconstructionQueue.empty())
{ {
return; return;
} }
DeconstructionEntry& front = m_deconstructionQueue.front(); DeconstructionEntry& front = m_state.deconstructionQueue.front();
// Guard: if the front entry's timer was never started, start it now. // Guard: if the front entry's timer was never started, start it now.
if (front.completesAt == 0) if (front.completesAt == 0)
@@ -789,15 +789,15 @@ void BuildingSystem::tickDeconstruction(Tick currentTick)
// Remove the building from the world and credit its refund (REQ-BLD-DECONSTRUCT). // Remove the building from the world and credit its refund (REQ-BLD-DECONSTRUCT).
// Belt/tunnel/splitter tiles were already unregistered when the building was // Belt/tunnel/splitter tiles were already unregistered when the building was
// queued (see deconstruct), so only tile occupancy and the record remain. // queued (see deconstruct), so only tile occupancy and the record remain.
for (std::vector<Building>::iterator it = m_buildings.begin(); for (std::vector<Building>::iterator it = m_state.buildings.begin();
it != m_buildings.end(); it != m_state.buildings.end();
++it) ++it)
{ {
if (it->id != front.id) { continue; } if (it->id != front.id) { continue; }
const BuildingDef* def = m_config.buildings.findBuildingDef(it->type); const BuildingDef* def = m_config.buildings.findBuildingDef(it->type);
m_grid.release(it->bodyCells); m_state.grid.release(it->bodyCells);
m_buildings.erase(it); m_state.buildings.erase(it);
if (def) if (def)
{ {
m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100); m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100);
@@ -805,7 +805,7 @@ void BuildingSystem::tickDeconstruction(Tick currentTick)
break; break;
} }
m_deconstructionQueue.pop_front(); m_state.deconstructionQueue.pop_front();
// Start the next queued deconstruction, if any. // Start the next queued deconstruction, if any.
startFrontDeconstruction(currentTick); startFrontDeconstruction(currentTick);
@@ -813,8 +813,8 @@ void BuildingSystem::tickDeconstruction(Tick currentTick)
void BuildingSystem::cancelDeconstruction(BuildingId id) void BuildingSystem::cancelDeconstruction(BuildingId id)
{ {
for (std::deque<DeconstructionEntry>::iterator it = m_deconstructionQueue.begin(); for (std::deque<DeconstructionEntry>::iterator it = m_state.deconstructionQueue.begin();
it != m_deconstructionQueue.end(); it != m_state.deconstructionQueue.end();
++it) ++it)
{ {
if (it->id != id) { continue; } if (it->id != id) { continue; }
@@ -828,7 +828,7 @@ void BuildingSystem::cancelDeconstruction(BuildingId id)
reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB); reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB);
} }
m_deconstructionQueue.erase(it); m_state.deconstructionQueue.erase(it);
// If the running front was removed, the new front (completesAt == 0) has // If the running front was removed, the new front (completesAt == 0) has
// its timer started by the next tickDeconstruction guard. // its timer started by the next tickDeconstruction guard.
return; return;
@@ -848,7 +848,7 @@ void BuildingSystem::tickBeltPull()
// (REQ-GW-BELT-SPEED, REQ-MAT-INPUT-INTAKE). // (REQ-GW-BELT-SPEED, REQ-MAT-INPUT-INTAKE).
const double progressPerTick = m_belts.getProgressPerTick_tpt(); const double progressPerTick = m_belts.getProgressPerTick_tpt();
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE). // A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; } if (building.queuedForDeconstruction) { continue; }
@@ -932,7 +932,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId,
const Port& outputPort, const Port& outputPort,
const Item& item) const Item& item)
{ {
const std::optional<BuildingId> ownerId = m_grid.findOwner(outputPort.tile); const std::optional<BuildingId> ownerId = m_state.grid.findOwner(outputPort.tile);
if (!ownerId.has_value() || *ownerId == producerId) if (!ownerId.has_value() || *ownerId == producerId)
{ {
return false; return false;
@@ -966,7 +966,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId,
void BuildingSystem::tickProduction(Tick currentTick) void BuildingSystem::tickProduction(Tick currentTick)
{ {
TRACE(); TRACE();
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE). // A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; } if (building.queuedForDeconstruction) { continue; }
@@ -1069,7 +1069,7 @@ void BuildingSystem::tickProduction(Tick currentTick)
void BuildingSystem::tickShipyardProduction(Tick currentTick) void BuildingSystem::tickShipyardProduction(Tick currentTick)
{ {
TRACE(); TRACE();
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE). // A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; } if (building.queuedForDeconstruction) { continue; }
@@ -1169,7 +1169,7 @@ void BuildingSystem::tickOutputBelts()
// same speed as real belts (REQ-GW-BELT-SPEED, REQ-MAT-OUTPUT-EMERGE). // same speed as real belts (REQ-GW-BELT-SPEED, REQ-MAT-OUTPUT-EMERGE).
const double progressPerTick = m_belts.getProgressPerTick_tpt(); const double progressPerTick = m_belts.getProgressPerTick_tpt();
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
// A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE). // A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE).
if (building.queuedForDeconstruction) { continue; } if (building.queuedForDeconstruction) { continue; }
@@ -1215,7 +1215,7 @@ void BuildingSystem::tickOutputBelts()
void BuildingSystem::forEachEmergingItem( void BuildingSystem::forEachEmergingItem(
const std::function<void(const ItemType&, QPointF)>& visit) const const std::function<void(const ItemType&, QPointF)>& visit) const
{ {
for (const Building& building : m_buildings) for (const Building& building : m_state.buildings)
{ {
for (std::size_t p = 0; p < building.outputPorts.size(); ++p) for (std::size_t p = 0; p < building.outputPorts.size(); ++p)
{ {
@@ -1237,7 +1237,7 @@ void BuildingSystem::forEachEmergingItem(
void BuildingSystem::forEachIncomingItem( void BuildingSystem::forEachIncomingItem(
const std::function<void(const ItemType&, QPointF)>& visit) const const std::function<void(const ItemType&, QPointF)>& visit) const
{ {
for (const Building& building : m_buildings) for (const Building& building : m_state.buildings)
{ {
for (std::size_t p = 0; p < building.inputPorts.size(); ++p) for (std::size_t p = 0; p < building.inputPorts.size(); ++p)
{ {
@@ -1262,7 +1262,7 @@ void BuildingSystem::forEachIncomingItem(
const Building* BuildingSystem::findBuilding(BuildingId id) const const Building* BuildingSystem::findBuilding(BuildingId id) const
{ {
for (const Building& building : m_buildings) for (const Building& building : m_state.buildings)
{ {
if (building.id == id) if (building.id == id)
{ {
@@ -1274,7 +1274,7 @@ const Building* BuildingSystem::findBuilding(BuildingId id) const
Building* BuildingSystem::findBuildingMutable(BuildingId id) Building* BuildingSystem::findBuildingMutable(BuildingId id)
{ {
for (Building& building : m_buildings) for (Building& building : m_state.buildings)
{ {
if (building.id == id) if (building.id == id)
{ {
@@ -1286,7 +1286,7 @@ Building* BuildingSystem::findBuildingMutable(BuildingId id)
const ConstructionSite* BuildingSystem::findSite(BuildingId id) const const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
{ {
for (const ConstructionSite& site : m_constructionQueue) for (const ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id == id) if (site.id == id)
{ {
@@ -1298,13 +1298,13 @@ const ConstructionSite* BuildingSystem::findSite(BuildingId id) const
std::vector<Building> BuildingSystem::getAllBuildings() const std::vector<Building> BuildingSystem::getAllBuildings() const
{ {
return m_buildings; return m_state.buildings;
} }
std::vector<ConstructionSite> BuildingSystem::getAllSites() const std::vector<ConstructionSite> BuildingSystem::getAllSites() const
{ {
return std::vector<ConstructionSite>(m_constructionQueue.begin(), return std::vector<ConstructionSite>(m_state.constructionQueue.begin(),
m_constructionQueue.end()); m_state.constructionQueue.end());
} }
namespace namespace
@@ -1328,7 +1328,7 @@ bool isProductionBuildingType(BuildingType type)
int BuildingSystem::getProductionBuildingCount() const int BuildingSystem::getProductionBuildingCount() const
{ {
int count = 0; int count = 0;
for (const Building& b : m_buildings) for (const Building& b : m_state.buildings)
{ {
if (isProductionBuildingType(b.type)) { ++count; } if (isProductionBuildingType(b.type)) { ++count; }
} }
@@ -1338,7 +1338,7 @@ int BuildingSystem::getProductionBuildingCount() const
int BuildingSystem::getActiveProductionBuildingCount() const int BuildingSystem::getActiveProductionBuildingCount() const
{ {
int count = 0; int count = 0;
for (const Building& b : m_buildings) for (const Building& b : m_state.buildings)
{ {
if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; } if (isProductionBuildingType(b.type) && b.production.has_value()) { ++count; }
} }
@@ -1489,7 +1489,7 @@ BuildingSystem::getProductionStatus(const Building& building) const
std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() const std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() const
{ {
std::vector<BeltTileInfo> result; std::vector<BeltTileInfo> result;
for (const Building& b : m_buildings) for (const Building& b : m_state.buildings)
{ {
if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter) if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter)
{ {
@@ -1520,7 +1520,7 @@ std::vector<BuildingSystem::BeltTileInfo> BuildingSystem::getAllBeltTiles() cons
bool BuildingSystem::isTileOccupied(QPoint tile) const bool BuildingSystem::isTileOccupied(QPoint tile) const
{ {
return m_grid.isOccupied(tile); return m_state.grid.isOccupied(tile);
} }
std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget( std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
@@ -1541,13 +1541,13 @@ std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
// All body cells must be occupied by the same entity. // All body cells must be occupied by the same entity.
const QPoint firstAbs = anchor + mask.bodyCells[0]; const QPoint firstAbs = anchor + mask.bodyCells[0];
const std::optional<BuildingId> firstOwner = m_grid.findOwner(firstAbs); const std::optional<BuildingId> firstOwner = m_state.grid.findOwner(firstAbs);
if (!firstOwner.has_value()) { return std::nullopt; } if (!firstOwner.has_value()) { return std::nullopt; }
const BuildingId candidateId = *firstOwner; const BuildingId candidateId = *firstOwner;
for (const QPoint& rel : mask.bodyCells) for (const QPoint& rel : mask.bodyCells)
{ {
const std::optional<BuildingId> owner = m_grid.findOwner(anchor + rel); const std::optional<BuildingId> owner = m_state.grid.findOwner(anchor + rel);
if (!owner.has_value() || *owner != candidateId) if (!owner.has_value() || *owner != candidateId)
{ {
return std::nullopt; return std::nullopt;
@@ -1555,14 +1555,14 @@ std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
} }
// Verify the candidate is the same building type with the same cell count. // Verify the candidate is the same building type with the same cell count.
for (const ConstructionSite& site : m_constructionQueue) for (const ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id != candidateId) { continue; } if (site.id != candidateId) { continue; }
if (site.type != type) { return std::nullopt; } if (site.type != type) { return std::nullopt; }
if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; } if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; }
return candidateId; return candidateId;
} }
for (const Building& b : m_buildings) for (const Building& b : m_state.buildings)
{ {
if (b.id != candidateId) { continue; } if (b.id != candidateId) { continue; }
if (b.type != type) { return std::nullopt; } if (b.type != type) { return std::nullopt; }
@@ -1576,7 +1576,7 @@ std::optional<BuildingId> BuildingSystem::findRotateInPlaceTarget(
void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation) void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
{ {
// Construction site path — just update rotation; no ports to recompute. // Construction site path — just update rotation; no ports to recompute.
for (ConstructionSite& site : m_constructionQueue) for (ConstructionSite& site : m_state.constructionQueue)
{ {
if (site.id == id) if (site.id == id)
{ {
@@ -1586,7 +1586,7 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation)
} }
// Operational building path. // Operational building path.
for (Building& b : m_buildings) for (Building& b : m_state.buildings)
{ {
if (b.id != id) { continue; } if (b.id != id) { continue; }
@@ -1643,7 +1643,7 @@ const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
{ {
const Building* best = nullptr; const Building* best = nullptr;
float bestDist = std::numeric_limits<float>::max(); float bestDist = std::numeric_limits<float>::max();
for (const Building& b : m_buildings) for (const Building& b : m_state.buildings)
{ {
if (b.type != type) if (b.type != type)
{ {
@@ -1664,7 +1664,7 @@ const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos,
bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId) bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId)
{ {
Building* bay = nullptr; Building* bay = nullptr;
for (Building& b : m_buildings) for (Building& b : m_state.buildings)
{ {
if (b.id == bayId) if (b.id == bayId)
{ {
@@ -1708,7 +1708,7 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
{ {
const QPoint absCell = anchor + cell; const QPoint absCell = anchor + cell;
building.bodyCells.push_back(absCell); building.bodyCells.push_back(absCell);
m_grid.occupy(absCell, id); m_state.grid.occupy(absCell, id);
} }
for (const Port& port : mask.outputPorts) for (const Port& port : mask.outputPorts)
{ {
@@ -1726,14 +1726,14 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type,
initSalvageBayBuffer(building); initSalvageBayBuffer(building);
} }
m_buildings.push_back(std::move(building)); m_state.buildings.push_back(std::move(building));
return id; return id;
} }
bool BuildingSystem::removeBuilding(BuildingId id) bool BuildingSystem::removeBuilding(BuildingId id)
{ {
for (std::vector<Building>::iterator it = m_buildings.begin(); for (std::vector<Building>::iterator it = m_state.buildings.begin();
it != m_buildings.end(); it != m_state.buildings.end();
++it) ++it)
{ {
if (it->id == id) if (it->id == id)
@@ -1743,8 +1743,8 @@ bool BuildingSystem::removeBuilding(BuildingId id)
{ {
m_belts.removeTile(it->anchor); m_belts.removeTile(it->anchor);
} }
m_grid.release(it->bodyCells); m_state.grid.release(it->bodyCells);
m_buildings.erase(it); m_state.buildings.erase(it);
return true; return true;
} }
} }
@@ -1753,7 +1753,7 @@ bool BuildingSystem::removeBuilding(BuildingId id)
void BuildingSystem::forEachBuilding(std::function<void(Building&)> fn) void BuildingSystem::forEachBuilding(std::function<void(Building&)> fn)
{ {
for (Building& b : m_buildings) for (Building& b : m_state.buildings)
{ {
fn(b); fn(b);
} }
@@ -1762,12 +1762,12 @@ void BuildingSystem::forEachBuilding(std::function<void(Building&)> fn)
void BuildingSystem::registerTileOccupancy(const std::vector<QPoint>& cells, void BuildingSystem::registerTileOccupancy(const std::vector<QPoint>& cells,
BuildingId ownerPlaceholder) BuildingId ownerPlaceholder)
{ {
m_grid.occupy(cells, ownerPlaceholder); m_state.grid.occupy(cells, ownerPlaceholder);
} }
void BuildingSystem::unregisterTileOccupancy(const std::vector<QPoint>& cells) void BuildingSystem::unregisterTileOccupancy(const std::vector<QPoint>& cells)
{ {
m_grid.release(cells); m_state.grid.release(cells);
} }
namespace namespace
@@ -1801,10 +1801,10 @@ void appendInputBuffer(Hasher& hasher, const InputBuffer& buffer)
void BuildingSystem::appendChecksum(Hasher& hasher) const void BuildingSystem::appendChecksum(Hasher& hasher) const
{ {
// m_buildings keeps a stable, deterministic order (append on build, swap-free // m_state.buildings keeps a stable, deterministic order (append on build, swap-free
// erase aside — both runs perform identical operations, so order matches). // erase aside — both runs perform identical operations, so order matches).
hasher.append(m_buildings.size()); hasher.append(m_state.buildings.size());
for (const Building& b : m_buildings) for (const Building& b : m_state.buildings)
{ {
hasher.append(b.id); hasher.append(b.id);
hasher.append(b.anchor); hasher.append(b.anchor);
@@ -1847,8 +1847,8 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
hasher.append(b.queuedForDeconstruction); hasher.append(b.queuedForDeconstruction);
} }
hasher.append(m_constructionQueue.size()); hasher.append(m_state.constructionQueue.size());
for (const ConstructionSite& s : m_constructionQueue) for (const ConstructionSite& s : m_state.constructionQueue)
{ {
hasher.append(s.id); hasher.append(s.id);
hasher.append(s.anchor); hasher.append(s.anchor);
@@ -1865,8 +1865,8 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
for (const ItemType& type : s.splitterFilterB) { hasher.append(type.id); } for (const ItemType& type : s.splitterFilterB) { hasher.append(type.id); }
} }
hasher.append(m_deconstructionQueue.size()); hasher.append(m_state.deconstructionQueue.size());
for (const DeconstructionEntry& e : m_deconstructionQueue) for (const DeconstructionEntry& e : m_state.deconstructionQueue)
{ {
hasher.append(e.id); hasher.append(e.id);
hasher.append(e.completesAt); hasher.append(e.completesAt);
@@ -1876,5 +1876,5 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const
for (const ItemType& type : e.splitterFilterB) { hasher.append(type.id); } for (const ItemType& type : e.splitterFilterB) { hasher.append(type.id); }
} }
m_grid.appendChecksum(hasher); m_state.grid.appendChecksum(hasher);
} }

View File

@@ -15,7 +15,7 @@
#include "BeltSystem.h" #include "BeltSystem.h"
#include "Building.h" #include "Building.h"
#include "BuildingGrid.h" #include "FactoryState.h"
#include "BuildingType.h" #include "BuildingType.h"
#include "BuildingId.h" #include "BuildingId.h"
#include "GameConfig.h" #include "GameConfig.h"
@@ -294,23 +294,8 @@ private:
std::mt19937& m_rng; std::mt19937& m_rng;
int m_asteroidWidth_tiles; int m_asteroidWidth_tiles;
std::vector<Building> m_buildings; // The factory's world data — buildings, queued work, tile ownership. Held here
std::deque<ConstructionSite> m_constructionQueue; // for now; the intent is for Simulation to own it and pass it into the tick
// methods, leaving this system stateless over it (see FactoryState.h).
// One pending demolition of a fully-built building (REQ-BLD-DECON-QUEUE). FactoryState m_state;
// completesAt == 0 means "queued but its timer has not started yet"
// (mirrors ConstructionSite). For a Splitter, the filters it had are captured
// here so cancelDeconstruction can restore them on re-registration.
struct DeconstructionEntry
{
BuildingId id = kInvalidBuildingId;
Tick completesAt = 0;
std::vector<ItemType> splitterFilterA;
std::vector<ItemType> splitterFilterB;
};
std::deque<DeconstructionEntry> m_deconstructionQueue;
// The authority on which building owns which tile; every placement and removal
// path claims and releases its body cells here.
BuildingGrid m_grid;
}; };

View File

@@ -13,6 +13,7 @@ SET(HDRS
${CMAKE_CURRENT_SOURCE_DIR}/Building.h ${CMAKE_CURRENT_SOURCE_DIR}/Building.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h
${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h
${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h
${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h ${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h
${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h

View File

@@ -0,0 +1,45 @@
#pragma once
#include <deque>
#include <vector>
#include "Building.h"
#include "BuildingGrid.h"
#include "BuildingId.h"
#include "ItemType.h"
#include "Tick.h"
// One pending demolition of a fully-built building (REQ-BLD-DECON-QUEUE).
// completesAt == 0 means "queued but its timer has not started yet"
// (mirrors ConstructionSite). For a Splitter, the filters it had are captured
// here so cancelDeconstruction can restore them on re-registration.
struct DeconstructionEntry
{
BuildingId id = kInvalidBuildingId;
Tick completesAt = 0;
std::vector<ItemType> splitterFilterA;
std::vector<ItemType> splitterFilterB;
};
// The factory's world data: every building, the work queued on them, and the
// tile ownership index. This is the buildings-side counterpart to EntityAdmin —
// data with no behaviour of its own beyond what BuildingGrid encapsulates.
//
// Buildings deliberately stay a plain vector rather than becoming EnTT entities
// (see docs/architecture.md). Separating this data from the systems that operate
// on it is not a step toward putting them in the entity model; it is the same
// data/behaviour split the ecs/system/ classes already follow, where world data
// arrives as a tick argument instead of being owned by the system.
//
// Owned by BuildingSystem for now. The intent is to hand ownership to Simulation
// and pass this into the tick methods, so the systems become stateless over it.
struct FactoryState
{
std::vector<Building> buildings;
std::deque<ConstructionSite> constructionQueue;
std::deque<DeconstructionEntry> deconstructionQueue;
// The authority on which building owns which tile; every placement and removal
// path claims and releases its body cells here.
BuildingGrid grid;
};