From 009f8c6d14058cc24311d05b47c9d2e9d2e309f1 Mon Sep 17 00:00:00 2001 From: Malte Langkabel Date: Wed, 5 Aug 2026 06:38:31 +0200 Subject: [PATCH] free the buffer setup and belt registration from BuildingSystem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prerequisite for ConstructionSystem completing a building itself rather than handing a finished site back: materialisation needs the buffer initialisers and the BeltSystem registration, and both were BuildingSystem members. initBuffers turned out to need nothing at all — it works purely on the Building and RecipeDef it is given. The other three need only the config. reregisterBeltTile takes BeltSystem and the config; it stays shared rather than moving, because cancelDeconstruction and rotateInPlace use it too and are staying on BuildingSystem. Verified with a golden-checksum capture before and after — all four sample ticks identical. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG --- src/lib/sim/BuildingBuffers.cpp | 173 ++++++++++++++++++++++++++++++ src/lib/sim/BuildingBuffers.h | 39 +++++++ src/lib/sim/BuildingSystem.cpp | 179 ++------------------------------ src/lib/sim/BuildingSystem.h | 8 +- src/lib/sim/CMakeLists.txt | 2 + 5 files changed, 224 insertions(+), 177 deletions(-) create mode 100644 src/lib/sim/BuildingBuffers.cpp create mode 100644 src/lib/sim/BuildingBuffers.h diff --git a/src/lib/sim/BuildingBuffers.cpp b/src/lib/sim/BuildingBuffers.cpp new file mode 100644 index 0000000..17736f1 --- /dev/null +++ b/src/lib/sim/BuildingBuffers.cpp @@ -0,0 +1,173 @@ +#include "BuildingBuffers.h" + +#include +#include + +#include "BuildingType.h" +#include "ItemType.h" +#include "ModulesConfig.h" +#include "ShipsConfig.h" + +void initBuffers(Building& b, const RecipeDef& recipe) +{ + b.inputBuffer.counts.clear(); + b.inputBuffer.caps.clear(); + for (const RecipeIngredient& ing : recipe.inputs) + { + const ItemType type{ing.item}; + b.inputBuffer.counts[type] = 0; + b.inputBuffer.caps[type] = 2 * ing.amount; + } + + b.outputBuffer.items.clear(); + if (b.type == BuildingType::ReprocessingPlant) + { + // 1× max-per-roll (REQ-MAT-OUTPUT-BUFFER-REPROCESSING). + int maxAmount = 0; + for (const RecipeOutput& out : recipe.outputs) + { + if (out.amount > maxAmount) + { + maxAmount = out.amount; + } + } + b.outputBuffer.capacity = maxAmount; + } + else + { + // 2× per-cycle output. + int totalAmount = 0; + for (const RecipeOutput& out : recipe.outputs) + { + totalAmount += out.amount; + } + b.outputBuffer.capacity = 2 * totalAmount; + } +} + +void initAutoBuffers(const GameConfig& config, Building& b) +{ + b.inputBuffer.counts.clear(); + b.inputBuffer.caps.clear(); + + // Union the inputs of every recipe of this building type; the cap for each + // item is twice the largest per-cycle requirement across those recipes. + // Output capacity follows the same rules as initBuffers: the Reprocessing + // Plant holds one cycle's max output (REQ-MAT-OUTPUT-BUFFER-REPROCESSING), + // other auto buildings hold twice the largest per-cycle output. + int outputCapacity = 0; + for (const RecipeDef& recipe : config.recipes.recipes) + { + if (recipe.building != b.type) + { + continue; + } + + for (const RecipeIngredient& ing : recipe.inputs) + { + const ItemType type{ing.item}; + b.inputBuffer.counts[type] = 0; + b.inputBuffer.caps[type] = + std::max(b.inputBuffer.caps[type], 2 * ing.amount); + } + + if (b.type == BuildingType::ReprocessingPlant) + { + int maxAmount = 0; + for (const RecipeOutput& out : recipe.outputs) + { + maxAmount = std::max(maxAmount, out.amount); + } + outputCapacity = std::max(outputCapacity, maxAmount); + } + else + { + int totalAmount = 0; + for (const RecipeOutput& out : recipe.outputs) + { + totalAmount += out.amount; + } + outputCapacity = std::max(outputCapacity, 2 * totalAmount); + } + } + + b.outputBuffer.items.clear(); + b.outputBuffer.capacity = outputCapacity; +} + +void initShipyardBuffers(const GameConfig& config, Building& b) +{ + b.inputBuffer.counts.clear(); + b.inputBuffer.caps.clear(); + b.outputBuffer.items.clear(); + b.outputBuffer.capacity = 0; + const ShipDef* def = config.ships.findShipDef(b.recipeId); + if (!def) + { + return; + } + for (const RecipeIngredient& ing : def->schematic.materials) + { + const ItemType type{ing.item}; + b.inputBuffer.counts[type] = 0; + b.inputBuffer.caps[type] = 2 * ing.amount; + } + if (b.shipLayout.has_value()) + { + for (const PlacedModule& pm : b.shipLayout->placedModules) + { + const ModuleDef* modDef = config.modules.findModuleDef(pm.moduleId); + if (!modDef) + { + continue; + } + for (const RecipeIngredient& ing : modDef->materials) + { + const ItemType type{ing.item}; + b.inputBuffer.counts.try_emplace(type, 0); + b.inputBuffer.caps[type] += 2 * ing.amount; + } + } + } +} + +void initSalvageBayBuffer(const GameConfig& config, Building& b) +{ + // Salvage Bay has no recipe-driven buffer; its output-buffer holding size for + // ship drop-off is config-defined (REQ-BLD-SALVAGE-BAY). + b.outputBuffer.items.clear(); + const BuildingDef* def = config.buildings.findBuildingDef(BuildingType::SalvageBay); + b.outputBuffer.capacity = + (def && def->outputBufferCapacity) ? *def->outputBufferCapacity : 0; +} + + +void reregisterBeltTile(BeltSystem& belts, const GameConfig& config, + const Building& building, + const std::vector& splitterFilterA, + const std::vector& splitterFilterB) +{ + switch (building.type) + { + case BuildingType::Belt: + belts.placeBelt(building.anchor, building.rotation); + break; + case BuildingType::Splitter: + assert(building.outputPorts.size() >= 2); + belts.placeSplitter(building.anchor, + building.outputPorts[0].direction, + building.outputPorts[1].direction); + belts.setSplitterFilters(building.anchor, splitterFilterA, splitterFilterB); + break; + case BuildingType::TunnelEntry: + belts.placeTunnelEntry(building.anchor, building.rotation, + config.world.tunnelMaxDistance_tiles); + break; + case BuildingType::TunnelExit: + belts.placeTunnelExit(building.anchor, building.rotation); + break; + default: + break; + } +} + diff --git a/src/lib/sim/BuildingBuffers.h b/src/lib/sim/BuildingBuffers.h new file mode 100644 index 0000000..1a61eda --- /dev/null +++ b/src/lib/sim/BuildingBuffers.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include "BeltSystem.h" +#include "Building.h" +#include "GameConfig.h" +#include "ItemType.h" +#include "RecipesConfig.h" + +// Setting a building up when it starts existing or is reconfigured: sizing its +// input/output buffers from what it will produce, and handing belt-like types back +// to BeltSystem. Free functions over the config and the building — they read no +// factory state, so both BuildingSystem and ConstructionSystem can use them. + +// Buffers for a building running one known recipe: inputs capped at twice each +// ingredient's per-cycle amount, output at twice the per-cycle total (one cycle's +// max for a Reprocessing Plant, REQ-MAT-OUTPUT-BUFFER-REPROCESSING). +void initBuffers(Building& b, const RecipeDef& recipe); + +// Buffers for an auto-recipe building (Smelter, Reprocessing Plant), unioned over +// every recipe of its type (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). +void initAutoBuffers(const GameConfig& config, Building& b); + +// Buffers for a shipyard: its schematic's materials plus those of every placed +// module (REQ-BLD-SHIPYARD). +void initShipyardBuffers(const GameConfig& config, Building& b); + +// The Salvage Bay holds no recipe inputs; its output capacity is config-defined +// (REQ-BLD-SALVAGE-BAY). +void initSalvageBayBuffer(const GameConfig& config, Building& b); + +// Registers a belt, splitter or tunnel end with BeltSystem. A splitter's filters +// live in BeltSystem and are lost by removeTile, so they are passed back in +// (REQ-BLD-SPLITTER). No-op for every other building type. +void reregisterBeltTile(BeltSystem& belts, const GameConfig& config, + const Building& building, + const std::vector& splitterFilterA, + const std::vector& splitterFilterB); diff --git a/src/lib/sim/BuildingSystem.cpp b/src/lib/sim/BuildingSystem.cpp index 36a5c68..3d7afcd 100644 --- a/src/lib/sim/BuildingSystem.cpp +++ b/src/lib/sim/BuildingSystem.cpp @@ -47,139 +47,6 @@ BuildingSystem::BuildingSystem(const GameConfig& config, // Private helpers // --------------------------------------------------------------------------- -void BuildingSystem::initBuffers(Building& b, const RecipeDef& recipe) const -{ - b.inputBuffer.counts.clear(); - b.inputBuffer.caps.clear(); - for (const RecipeIngredient& ing : recipe.inputs) - { - const ItemType type{ing.item}; - b.inputBuffer.counts[type] = 0; - b.inputBuffer.caps[type] = 2 * ing.amount; - } - - b.outputBuffer.items.clear(); - if (b.type == BuildingType::ReprocessingPlant) - { - // 1× max-per-roll (REQ-MAT-OUTPUT-BUFFER-REPROCESSING). - int maxAmount = 0; - for (const RecipeOutput& out : recipe.outputs) - { - if (out.amount > maxAmount) - { - maxAmount = out.amount; - } - } - b.outputBuffer.capacity = maxAmount; - } - else - { - // 2× per-cycle output. - int totalAmount = 0; - for (const RecipeOutput& out : recipe.outputs) - { - totalAmount += out.amount; - } - b.outputBuffer.capacity = 2 * totalAmount; - } -} - -void BuildingSystem::initAutoBuffers(Building& b) const -{ - b.inputBuffer.counts.clear(); - b.inputBuffer.caps.clear(); - - // Union the inputs of every recipe of this building type; the cap for each - // item is twice the largest per-cycle requirement across those recipes. - // Output capacity follows the same rules as initBuffers: the Reprocessing - // Plant holds one cycle's max output (REQ-MAT-OUTPUT-BUFFER-REPROCESSING), - // other auto buildings hold twice the largest per-cycle output. - int outputCapacity = 0; - for (const RecipeDef& recipe : m_config.recipes.recipes) - { - if (recipe.building != b.type) - { - continue; - } - - for (const RecipeIngredient& ing : recipe.inputs) - { - const ItemType type{ing.item}; - b.inputBuffer.counts[type] = 0; - b.inputBuffer.caps[type] = - std::max(b.inputBuffer.caps[type], 2 * ing.amount); - } - - if (b.type == BuildingType::ReprocessingPlant) - { - int maxAmount = 0; - for (const RecipeOutput& out : recipe.outputs) - { - maxAmount = std::max(maxAmount, out.amount); - } - outputCapacity = std::max(outputCapacity, maxAmount); - } - else - { - int totalAmount = 0; - for (const RecipeOutput& out : recipe.outputs) - { - totalAmount += out.amount; - } - outputCapacity = std::max(outputCapacity, 2 * totalAmount); - } - } - - b.outputBuffer.items.clear(); - b.outputBuffer.capacity = outputCapacity; -} - -void BuildingSystem::initShipyardBuffers(Building& b) const -{ - b.inputBuffer.counts.clear(); - b.inputBuffer.caps.clear(); - b.outputBuffer.items.clear(); - b.outputBuffer.capacity = 0; - const ShipDef* def = m_config.ships.findShipDef(b.recipeId); - if (!def) - { - return; - } - for (const RecipeIngredient& ing : def->schematic.materials) - { - const ItemType type{ing.item}; - b.inputBuffer.counts[type] = 0; - b.inputBuffer.caps[type] = 2 * ing.amount; - } - if (b.shipLayout.has_value()) - { - for (const PlacedModule& pm : b.shipLayout->placedModules) - { - const ModuleDef* modDef = m_config.modules.findModuleDef(pm.moduleId); - if (!modDef) - { - continue; - } - for (const RecipeIngredient& ing : modDef->materials) - { - const ItemType type{ing.item}; - b.inputBuffer.counts.try_emplace(type, 0); - b.inputBuffer.caps[type] += 2 * ing.amount; - } - } - } -} - -void BuildingSystem::initSalvageBayBuffer(Building& b) const -{ - // Salvage Bay has no recipe-driven buffer; its output-buffer holding size for - // ship drop-off is config-defined (REQ-BLD-SALVAGE-BAY). - b.outputBuffer.items.clear(); - const BuildingDef* def = m_config.buildings.findBuildingDef(BuildingType::SalvageBay); - b.outputBuffer.capacity = - (def && def->outputBufferCapacity) ? *def->outputBufferCapacity : 0; -} - std::vector BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe) { @@ -397,7 +264,7 @@ void BuildingSystem::setRecipe(FactoryState& state, BuildingId id, const std::st { if (building.type == BuildingType::Shipyard) { - initShipyardBuffers(building); + initShipyardBuffers(m_config, building); } else { @@ -441,7 +308,7 @@ void BuildingSystem::setShipLayout(FactoryState& state, BuildingId id, const Shi for (std::vector& lane : building.incomingItems) { lane.clear(); } if (!building.recipeId.empty() && building.type == BuildingType::Shipyard) { - initShipyardBuffers(building); + initShipyardBuffers(m_config, building); } return; } @@ -525,19 +392,19 @@ void BuildingSystem::tickConstruction(FactoryState& state, Tick currentTick) if (building.type == BuildingType::SalvageBay) { - initSalvageBayBuffer(building); + initSalvageBayBuffer(m_config, building); } else if (isAutoRecipeBuildingType(building.type)) { // Smelter/Reprocessing Plant need no recipe selection; buffers are set // up from all recipes of the type (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). - initAutoBuffers(building); + initAutoBuffers(m_config, building); } else if (!building.recipeId.empty()) { if (building.type == BuildingType::Shipyard) { - initShipyardBuffers(building); + initShipyardBuffers(m_config, building); } else { @@ -551,7 +418,7 @@ void BuildingSystem::tickConstruction(FactoryState& state, Tick currentTick) // Register with BeltSystem before the move (mask/building stays valid). Any // filters configured while under construction carry over (REQ-BLD-SITE-CONFIG). - reregisterBeltTile(building, front.splitterFilterA, front.splitterFilterB); + reregisterBeltTile(m_belts, m_config, building, front.splitterFilterA, front.splitterFilterB); state.buildings.push_back(std::move(building)); @@ -570,34 +437,6 @@ void BuildingSystem::tickConstruction(FactoryState& state, Tick currentTick) } } -void BuildingSystem::reregisterBeltTile(const Building& building, - const std::vector& splitterFilterA, - const std::vector& splitterFilterB) -{ - switch (building.type) - { - case BuildingType::Belt: - m_belts.placeBelt(building.anchor, building.rotation); - break; - case BuildingType::Splitter: - assert(building.outputPorts.size() >= 2); - m_belts.placeSplitter(building.anchor, - building.outputPorts[0].direction, - building.outputPorts[1].direction); - m_belts.setSplitterFilters(building.anchor, splitterFilterA, splitterFilterB); - break; - case BuildingType::TunnelEntry: - m_belts.placeTunnelEntry(building.anchor, building.rotation, - m_config.world.tunnelMaxDistance_tiles); - break; - case BuildingType::TunnelExit: - m_belts.placeTunnelExit(building.anchor, building.rotation); - break; - default: - break; - } -} - void BuildingSystem::tickDeconstruction(FactoryState& state, Tick currentTick) { TRACE(); @@ -659,7 +498,7 @@ void BuildingSystem::cancelDeconstruction(FactoryState& state, BuildingId id) if (Building* building = findBuilding(state, id)) { building->queuedForDeconstruction = false; - reregisterBeltTile(*building, it->splitterFilterA, it->splitterFilterB); + reregisterBeltTile(m_belts, m_config, *building, it->splitterFilterA, it->splitterFilterB); } state.deconstructionQueue.erase(it); @@ -1151,7 +990,7 @@ void BuildingSystem::rotateInPlace(FactoryState& state, BuildingId id, Rotation } m_belts.removeTile(b.anchor); - reregisterBeltTile(b, splitterFilterA, splitterFilterB); + reregisterBeltTile(m_belts, m_config, b, splitterFilterA, splitterFilterB); } return; @@ -1191,7 +1030,7 @@ BuildingId BuildingSystem::placeImmediate(FactoryState& state, BuildingType type if (type == BuildingType::SalvageBay) { - initSalvageBayBuffer(building); + initSalvageBayBuffer(m_config, building); } state.buildings.push_back(std::move(building)); diff --git a/src/lib/sim/BuildingSystem.h b/src/lib/sim/BuildingSystem.h index 9ead72e..1e67a76 100644 --- a/src/lib/sim/BuildingSystem.h +++ b/src/lib/sim/BuildingSystem.h @@ -16,6 +16,7 @@ #include "BeltSystem.h" #include "Building.h" #include "FactoryState.h" +#include "BuildingBuffers.h" #include "PlacementRules.h" #include "ProductionRules.h" #include "BuildingType.h" @@ -188,9 +189,6 @@ private: // Registers a belt/splitter/tunnel building's tile with the belt subsystem // (on construction completion, or when un-queuing a deconstruction). No-op for // non-belt-subsystem types. Splitter filters are (re)applied after placement. - void reregisterBeltTile(const Building& building, - const std::vector& splitterFilterA, - const std::vector& splitterFilterB); // True if the consumer would accept `type` at the given input port right now: // it is a required input (or a building block for the HQ), the reservation-aware @@ -221,13 +219,9 @@ private: // (ignoring output-buffer space); drives the Starved/Blocked distinction of // the status light (REQ-UI-STATUS-LIGHT). - void initBuffers(Building& b, const RecipeDef& recipe) const; // Buffers for an auto-recipe building (Smelter, Reprocessing Plant): input // caps span the union of every recipe of the building's type; no player // recipe is selected (REQ-BLD-SMELTER, REQ-BLD-REPROCESSING). - void initAutoBuffers(Building& b) const; - void initShipyardBuffers(Building& b) const; - void initSalvageBayBuffer(Building& b) const; // Core input-edge scan shared by operational buildings and construction sites. std::vector rollReprocessingOutput(const RecipeDef& recipe); diff --git a/src/lib/sim/CMakeLists.txt b/src/lib/sim/CMakeLists.txt index 351ed5b..4ad6779 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}/BuildingBuffers.h ${CMAKE_CURRENT_SOURCE_DIR}/FactoryState.h ${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.h ${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.h @@ -42,6 +43,7 @@ SET(SRCS ${CMAKE_CURRENT_SOURCE_DIR}/BeltSystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuildingConfig.cpp ${CMAKE_CURRENT_SOURCE_DIR}/BuildingGrid.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/BuildingBuffers.cpp ${CMAKE_CURRENT_SOURCE_DIR}/FactoryQueries.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ProductionRules.cpp ${CMAKE_CURRENT_SOURCE_DIR}/PlacementRules.cpp