diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 647505a..124408d 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -308,7 +308,7 @@ std::optional BuildingSystem::place(BuildingType type, QPoint anchor for (const QPoint& cell : mask.bodyCells) { const QPoint absCell = anchor + cell; - m_grid.occupy(absCell, id); + m_state.grid.occupy(absCell, id); } // Build construction site. @@ -323,13 +323,13 @@ std::optional BuildingSystem::place(BuildingType type, QPoint anchor site.bodyCells.push_back(anchor + cell); } - if (m_constructionQueue.empty()) + if (m_state.constructionQueue.empty()) { site.completesAt = currentTick + secondsToTicks(def->constructionTimeSeconds); } // 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; } @@ -405,15 +405,15 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick) { // Construction site? Removed instantly with the full refund; never queued // for deconstruction (REQ-BLD-DECONSTRUCT). - for (std::deque::iterator it = m_constructionQueue.begin(); - it != m_constructionQueue.end(); + for (std::deque::iterator it = m_state.constructionQueue.begin(); + it != m_state.constructionQueue.end(); ++it) { if (it->id == id) { const BuildingDef* def = m_config.buildings.findBuildingDef(it->type); - m_grid.release(it->bodyCells); - m_constructionQueue.erase(it); + m_state.grid.release(it->bodyCells); + m_state.constructionQueue.erase(it); if (def) { return def->cost; @@ -425,7 +425,7 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick) // Operational building? Append it to the deconstruction queue rather than // removing it now; the partial refund is credited on completion in // tickDeconstruction (REQ-BLD-DECON-QUEUE). - for (Building& building : m_buildings) + for (Building& building : m_state.buildings) { if (building.id != id) { continue; } if (building.queuedForDeconstruction) { return 0; } // already queued @@ -453,8 +453,8 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick) m_belts.removeTile(building.anchor); } - const bool wasEmpty = m_deconstructionQueue.empty(); - m_deconstructionQueue.push_back(std::move(entry)); + const bool wasEmpty = m_state.deconstructionQueue.empty(); + m_state.deconstructionQueue.push_back(std::move(entry)); if (wasEmpty) { startFrontDeconstruction(currentTick); @@ -467,8 +467,8 @@ int BuildingSystem::deconstruct(BuildingId id, Tick currentTick) void BuildingSystem::startFrontDeconstruction(Tick currentTick) { - if (m_deconstructionQueue.empty()) { return; } - DeconstructionEntry& front = m_deconstructionQueue.front(); + if (m_state.deconstructionQueue.empty()) { return; } + DeconstructionEntry& front = m_state.deconstructionQueue.front(); if (front.completesAt == 0) { front.completesAt = @@ -483,7 +483,7 @@ void BuildingSystem::startFrontDeconstruction(Tick currentTick) void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId) { // Construction site: store recipe for when building completes. - for (ConstructionSite& site : m_constructionQueue) + for (ConstructionSite& site : m_state.constructionQueue) { if (site.id == id) { @@ -506,7 +506,7 @@ void BuildingSystem::setRecipe(BuildingId id, const std::string& recipeId) } // Operational building: clear buffers and re-init. - for (Building& building : m_buildings) + for (Building& building : m_state.buildings) { 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) { - for (ConstructionSite& site : m_constructionQueue) + for (ConstructionSite& site : m_state.constructionQueue) { 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) { @@ -594,7 +594,7 @@ void BuildingSystem::setShipLayout(BuildingId id, const ShipLayoutConfig& layout std::optional 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.type != BuildingType::Splitter) { return std::nullopt; } @@ -618,7 +618,7 @@ void BuildingSystem::setSiteSplitterFilters(BuildingId id, const std::vector& filterA, const std::vector& filterB) { - for (ConstructionSite& site : m_constructionQueue) + for (ConstructionSite& site : m_state.constructionQueue) { if (site.id == id && site.type == BuildingType::Splitter) { @@ -636,12 +636,12 @@ void BuildingSystem::setSiteSplitterFilters(BuildingId id, void BuildingSystem::tickConstruction(Tick currentTick) { TRACE(); - if (m_constructionQueue.empty()) + if (m_state.constructionQueue.empty()) { return; } - ConstructionSite& front = m_constructionQueue.front(); + ConstructionSite& front = m_state.constructionQueue.front(); // Guard: if somehow the front site was never started, start it now. if (front.completesAt == 0) @@ -719,18 +719,18 @@ void BuildingSystem::tickConstruction(Tick currentTick) // filters configured while under construction carry over (REQ-BLD-SITE-CONFIG). 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. - if (!m_constructionQueue.empty() && m_constructionQueue.front().completesAt == 0) + if (!m_state.constructionQueue.empty() && m_state.constructionQueue.front().completesAt == 0) { const BuildingDef* nextDef = - m_config.buildings.findBuildingDef(m_constructionQueue.front().type); + m_config.buildings.findBuildingDef(m_state.constructionQueue.front().type); if (nextDef) { - m_constructionQueue.front().completesAt = + m_state.constructionQueue.front().completesAt = currentTick + secondsToTicks(nextDef->constructionTimeSeconds); } } @@ -767,12 +767,12 @@ void BuildingSystem::reregisterBeltTile(const Building& building, void BuildingSystem::tickDeconstruction(Tick currentTick) { TRACE(); - if (m_deconstructionQueue.empty()) + if (m_state.deconstructionQueue.empty()) { 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. 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). // Belt/tunnel/splitter tiles were already unregistered when the building was // queued (see deconstruct), so only tile occupancy and the record remain. - for (std::vector::iterator it = m_buildings.begin(); - it != m_buildings.end(); + for (std::vector::iterator it = m_state.buildings.begin(); + it != m_state.buildings.end(); ++it) { if (it->id != front.id) { continue; } const BuildingDef* def = m_config.buildings.findBuildingDef(it->type); - m_grid.release(it->bodyCells); - m_buildings.erase(it); + m_state.grid.release(it->bodyCells); + m_state.buildings.erase(it); if (def) { m_addBuildingBlocks(def->cost * m_config.world.refundPercentage / 100); @@ -805,7 +805,7 @@ void BuildingSystem::tickDeconstruction(Tick currentTick) break; } - m_deconstructionQueue.pop_front(); + m_state.deconstructionQueue.pop_front(); // Start the next queued deconstruction, if any. startFrontDeconstruction(currentTick); @@ -813,8 +813,8 @@ void BuildingSystem::tickDeconstruction(Tick currentTick) void BuildingSystem::cancelDeconstruction(BuildingId id) { - for (std::deque::iterator it = m_deconstructionQueue.begin(); - it != m_deconstructionQueue.end(); + for (std::deque::iterator it = m_state.deconstructionQueue.begin(); + it != m_state.deconstructionQueue.end(); ++it) { if (it->id != id) { continue; } @@ -828,7 +828,7 @@ void BuildingSystem::cancelDeconstruction(BuildingId id) 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 // its timer started by the next tickDeconstruction guard. return; @@ -848,7 +848,7 @@ void BuildingSystem::tickBeltPull() // (REQ-GW-BELT-SPEED, REQ-MAT-INPUT-INTAKE). 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). if (building.queuedForDeconstruction) { continue; } @@ -932,7 +932,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId, const Port& outputPort, const Item& item) { - const std::optional ownerId = m_grid.findOwner(outputPort.tile); + const std::optional ownerId = m_state.grid.findOwner(outputPort.tile); if (!ownerId.has_value() || *ownerId == producerId) { return false; @@ -966,7 +966,7 @@ bool BuildingSystem::tryDirectCoupleDeposit(BuildingId producerId, void BuildingSystem::tickProduction(Tick currentTick) { TRACE(); - for (Building& building : m_buildings) + for (Building& building : m_state.buildings) { // A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE). if (building.queuedForDeconstruction) { continue; } @@ -1069,7 +1069,7 @@ void BuildingSystem::tickProduction(Tick currentTick) void BuildingSystem::tickShipyardProduction(Tick currentTick) { TRACE(); - for (Building& building : m_buildings) + for (Building& building : m_state.buildings) { // A building queued for deconstruction stops operating (REQ-BLD-DECON-QUEUE). if (building.queuedForDeconstruction) { continue; } @@ -1169,7 +1169,7 @@ void BuildingSystem::tickOutputBelts() // same speed as real belts (REQ-GW-BELT-SPEED, REQ-MAT-OUTPUT-EMERGE). 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). if (building.queuedForDeconstruction) { continue; } @@ -1215,7 +1215,7 @@ void BuildingSystem::tickOutputBelts() void BuildingSystem::forEachEmergingItem( const std::function& 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) { @@ -1237,7 +1237,7 @@ void BuildingSystem::forEachEmergingItem( void BuildingSystem::forEachIncomingItem( const std::function& 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) { @@ -1262,7 +1262,7 @@ void BuildingSystem::forEachIncomingItem( const Building* BuildingSystem::findBuilding(BuildingId id) const { - for (const Building& building : m_buildings) + for (const Building& building : m_state.buildings) { if (building.id == id) { @@ -1274,7 +1274,7 @@ const Building* BuildingSystem::findBuilding(BuildingId id) const Building* BuildingSystem::findBuildingMutable(BuildingId id) { - for (Building& building : m_buildings) + for (Building& building : m_state.buildings) { if (building.id == id) { @@ -1286,7 +1286,7 @@ Building* BuildingSystem::findBuildingMutable(BuildingId id) const ConstructionSite* BuildingSystem::findSite(BuildingId id) const { - for (const ConstructionSite& site : m_constructionQueue) + for (const ConstructionSite& site : m_state.constructionQueue) { if (site.id == id) { @@ -1298,13 +1298,13 @@ const ConstructionSite* BuildingSystem::findSite(BuildingId id) const std::vector BuildingSystem::getAllBuildings() const { - return m_buildings; + return m_state.buildings; } std::vector BuildingSystem::getAllSites() const { - return std::vector(m_constructionQueue.begin(), - m_constructionQueue.end()); + return std::vector(m_state.constructionQueue.begin(), + m_state.constructionQueue.end()); } namespace @@ -1328,7 +1328,7 @@ bool isProductionBuildingType(BuildingType type) int BuildingSystem::getProductionBuildingCount() const { int count = 0; - for (const Building& b : m_buildings) + for (const Building& b : m_state.buildings) { if (isProductionBuildingType(b.type)) { ++count; } } @@ -1338,7 +1338,7 @@ int BuildingSystem::getProductionBuildingCount() const int BuildingSystem::getActiveProductionBuildingCount() const { 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; } } @@ -1489,7 +1489,7 @@ BuildingSystem::getProductionStatus(const Building& building) const std::vector BuildingSystem::getAllBeltTiles() const { std::vector result; - for (const Building& b : m_buildings) + for (const Building& b : m_state.buildings) { if (b.type != BuildingType::Belt && b.type != BuildingType::Splitter) { @@ -1520,7 +1520,7 @@ std::vector BuildingSystem::getAllBeltTiles() cons bool BuildingSystem::isTileOccupied(QPoint tile) const { - return m_grid.isOccupied(tile); + return m_state.grid.isOccupied(tile); } std::optional BuildingSystem::findRotateInPlaceTarget( @@ -1541,13 +1541,13 @@ std::optional BuildingSystem::findRotateInPlaceTarget( // All body cells must be occupied by the same entity. const QPoint firstAbs = anchor + mask.bodyCells[0]; - const std::optional firstOwner = m_grid.findOwner(firstAbs); + const std::optional firstOwner = m_state.grid.findOwner(firstAbs); if (!firstOwner.has_value()) { return std::nullopt; } const BuildingId candidateId = *firstOwner; for (const QPoint& rel : mask.bodyCells) { - const std::optional owner = m_grid.findOwner(anchor + rel); + const std::optional owner = m_state.grid.findOwner(anchor + rel); if (!owner.has_value() || *owner != candidateId) { return std::nullopt; @@ -1555,14 +1555,14 @@ std::optional BuildingSystem::findRotateInPlaceTarget( } // 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.type != type) { return std::nullopt; } if (site.bodyCells.size() != mask.bodyCells.size()) { return std::nullopt; } return candidateId; } - for (const Building& b : m_buildings) + for (const Building& b : m_state.buildings) { if (b.id != candidateId) { continue; } if (b.type != type) { return std::nullopt; } @@ -1576,7 +1576,7 @@ std::optional BuildingSystem::findRotateInPlaceTarget( void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation) { // 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) { @@ -1586,7 +1586,7 @@ void BuildingSystem::rotateInPlace(BuildingId id, Rotation newRotation) } // Operational building path. - for (Building& b : m_buildings) + for (Building& b : m_state.buildings) { if (b.id != id) { continue; } @@ -1643,7 +1643,7 @@ const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos, { const Building* best = nullptr; float bestDist = std::numeric_limits::max(); - for (const Building& b : m_buildings) + for (const Building& b : m_state.buildings) { if (b.type != type) { @@ -1664,7 +1664,7 @@ const Building* BuildingSystem::findNearestBuilding(QVector2D worldPos, bool BuildingSystem::deliverScrapToSalvageBay(BuildingId bayId) { Building* bay = nullptr; - for (Building& b : m_buildings) + for (Building& b : m_state.buildings) { if (b.id == bayId) { @@ -1708,7 +1708,7 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type, { const QPoint absCell = anchor + cell; building.bodyCells.push_back(absCell); - m_grid.occupy(absCell, id); + m_state.grid.occupy(absCell, id); } for (const Port& port : mask.outputPorts) { @@ -1726,14 +1726,14 @@ BuildingId BuildingSystem::placeImmediate(BuildingType type, initSalvageBayBuffer(building); } - m_buildings.push_back(std::move(building)); + m_state.buildings.push_back(std::move(building)); return id; } bool BuildingSystem::removeBuilding(BuildingId id) { - for (std::vector::iterator it = m_buildings.begin(); - it != m_buildings.end(); + for (std::vector::iterator it = m_state.buildings.begin(); + it != m_state.buildings.end(); ++it) { if (it->id == id) @@ -1743,8 +1743,8 @@ bool BuildingSystem::removeBuilding(BuildingId id) { m_belts.removeTile(it->anchor); } - m_grid.release(it->bodyCells); - m_buildings.erase(it); + m_state.grid.release(it->bodyCells); + m_state.buildings.erase(it); return true; } } @@ -1753,7 +1753,7 @@ bool BuildingSystem::removeBuilding(BuildingId id) void BuildingSystem::forEachBuilding(std::function fn) { - for (Building& b : m_buildings) + for (Building& b : m_state.buildings) { fn(b); } @@ -1762,12 +1762,12 @@ void BuildingSystem::forEachBuilding(std::function fn) void BuildingSystem::registerTileOccupancy(const std::vector& cells, BuildingId ownerPlaceholder) { - m_grid.occupy(cells, ownerPlaceholder); + m_state.grid.occupy(cells, ownerPlaceholder); } void BuildingSystem::unregisterTileOccupancy(const std::vector& cells) { - m_grid.release(cells); + m_state.grid.release(cells); } namespace @@ -1801,10 +1801,10 @@ void appendInputBuffer(Hasher& hasher, const InputBuffer& buffer) 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). - hasher.append(m_buildings.size()); - for (const Building& b : m_buildings) + hasher.append(m_state.buildings.size()); + for (const Building& b : m_state.buildings) { hasher.append(b.id); hasher.append(b.anchor); @@ -1847,8 +1847,8 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const hasher.append(b.queuedForDeconstruction); } - hasher.append(m_constructionQueue.size()); - for (const ConstructionSite& s : m_constructionQueue) + hasher.append(m_state.constructionQueue.size()); + for (const ConstructionSite& s : m_state.constructionQueue) { hasher.append(s.id); hasher.append(s.anchor); @@ -1865,8 +1865,8 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const for (const ItemType& type : s.splitterFilterB) { hasher.append(type.id); } } - hasher.append(m_deconstructionQueue.size()); - for (const DeconstructionEntry& e : m_deconstructionQueue) + hasher.append(m_state.deconstructionQueue.size()); + for (const DeconstructionEntry& e : m_state.deconstructionQueue) { hasher.append(e.id); hasher.append(e.completesAt); @@ -1876,5 +1876,5 @@ void BuildingSystem::appendChecksum(Hasher& hasher) const for (const ItemType& type : e.splitterFilterB) { hasher.append(type.id); } } - m_grid.appendChecksum(hasher); + m_state.grid.appendChecksum(hasher); } diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index 88cc228..3a70f0c 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -15,7 +15,7 @@ #include "BeltSystem.h" #include "Building.h" -#include "BuildingGrid.h" +#include "FactoryState.h" #include "BuildingType.h" #include "BuildingId.h" #include "GameConfig.h" @@ -294,23 +294,8 @@ private: std::mt19937& m_rng; int m_asteroidWidth_tiles; - std::vector m_buildings; - std::deque m_constructionQueue; - - // 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 splitterFilterA; - std::vector splitterFilterB; - }; - std::deque 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; + // The factory's world data — buildings, queued work, tile ownership. Held here + // 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). + FactoryState m_state; }; diff --git a/src/lib/sim/CMakeLists.txt b/src/lib/sim/CMakeLists.txt index c0b8fcd..6c2f0ed 100644 --- a/src/lib/sim/CMakeLists.txt +++ b/src/lib/sim/CMakeLists.txt @@ -13,6 +13,7 @@ SET(HDRS ${CMAKE_CURRENT_SOURCE_DIR}/Building.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.h + ${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h ${CMAKE_CURRENT_SOURCE_DIR}/BuildingSystem.h ${CMAKE_CURRENT_SOURCE_DIR}/EntityHitTest.h ${CMAKE_CURRENT_SOURCE_DIR}/ShipLayout.h diff --git a/src/lib/sim/FactoryState.h b/src/lib/sim/FactoryState.h new file mode 100644 index 0000000..3624076 --- /dev/null +++ b/src/lib/sim/FactoryState.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +#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 splitterFilterA; + std::vector 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 buildings; + std::deque constructionQueue; + std::deque deconstructionQueue; + + // The authority on which building owns which tile; every placement and removal + // path claims and releases its body cells here. + BuildingGrid grid; +};