Use std::optional instead of sentinel values for absent data

This commit is contained in:
2026-07-20 20:27:20 +02:00
parent 1cdafb7bcd
commit 9622fa4345
32 changed files with 228 additions and 209 deletions

View File

@@ -346,7 +346,7 @@ std::vector<Item> BuildingSystem::rollReprocessingOutput(const RecipeDef& recipe
// Placement
// ---------------------------------------------------------------------------
BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
std::optional<BuildingId> BuildingSystem::place(BuildingType type, QPoint anchor,
Rotation rotation, Tick currentTick)
{
const BuildingDef* def = findBuildingDef(type);
@@ -356,7 +356,7 @@ BuildingId BuildingSystem::place(BuildingType type, QPoint anchor,
// Reject placements that fall outside the world (REQ-BLD-PLACE-VALID).
if (!bodyCellsWithinWorldBounds(mask.bodyCells, anchor))
{
return kInvalidBuildingId;
return std::nullopt;
}
const BuildingId id = m_allocateBuildingId();

View File

@@ -43,13 +43,13 @@ public:
std::mt19937& rng);
// -- Placement / demolish ------------------------------------------------
// Returns the new entity id, or kInvalidBuildingId if the placement falls
// outside the world bounds (vertical extent and asteroid left edge). Belt
// and Splitter register with BeltSystem directly; other types enter the
// construction queue. Terrain type (A vs S) is NOT checked here so that
// tests can stage arbitrary layouts; the player-facing entry point
// Returns the new entity id, or nullopt if the placement falls outside the
// world bounds (vertical extent and asteroid left edge). Belt and Splitter
// register with BeltSystem directly; other types enter the construction
// queue. Terrain type (A vs S) is NOT checked here so that tests can stage
// arbitrary layouts; the player-facing entry point
// (Simulation::tryPlaceBuilding) enforces the full rule via isPlacementValid.
BuildingId place(BuildingType type, QPoint anchor, Rotation rotation,
std::optional<BuildingId> place(BuildingType type, QPoint anchor, Rotation rotation,
Tick currentTick);
// Returns true if the placement satisfies REQ-BLD-PLACE-VALID terrain and

View File

@@ -75,28 +75,28 @@ struct PlaceBuildingCommand : Command
struct DemolishCommand : Command
{
DemolishCommand() : Command(CommandKind::Demolish) {}
BuildingId id = kInvalidBuildingId;
std::optional<BuildingId> id;
};
struct RotateInPlaceCommand : Command
{
RotateInPlaceCommand() : Command(CommandKind::RotateInPlace) {}
BuildingId id = kInvalidBuildingId;
Rotation newRotation = Rotation::East;
std::optional<BuildingId> id;
Rotation newRotation = Rotation::East;
};
struct SetRecipeCommand : Command
{
SetRecipeCommand() : Command(CommandKind::SetRecipe) {}
BuildingId id = kInvalidBuildingId;
std::string recipeId;
std::optional<BuildingId> id;
std::string recipeId;
};
struct SetShipLayoutCommand : Command
{
SetShipLayoutCommand() : Command(CommandKind::SetShipLayout) {}
BuildingId id = kInvalidBuildingId;
ShipLayoutConfig layout;
std::optional<BuildingId> id;
ShipLayoutConfig layout;
};
// Splitter filters for a queued / under-construction Splitter site (configured by
@@ -104,8 +104,8 @@ struct SetShipLayoutCommand : Command
struct SetSiteSplitterFiltersCommand : Command
{
SetSiteSplitterFiltersCommand() : Command(CommandKind::SetSiteSplitterFilters) {}
BuildingId id = kInvalidBuildingId;
std::vector<ItemType> filterA;
std::optional<BuildingId> id;
std::vector<ItemType> filterA;
std::vector<ItemType> filterB;
};

View File

@@ -129,24 +129,24 @@ std::string serializeCommand(const Command& command)
break;
}
case CommandKind::Demolish:
out << "demolish " << static_cast<const DemolishCommand&>(command).id;
out << "demolish " << static_cast<const DemolishCommand&>(command).id.value();
break;
case CommandKind::RotateInPlace:
{
const RotateInPlaceCommand& c = static_cast<const RotateInPlaceCommand&>(command);
out << "rotate " << c.id << ' ' << rotationToChar(c.newRotation);
out << "rotate " << c.id.value() << ' ' << rotationToChar(c.newRotation);
break;
}
case CommandKind::SetRecipe:
{
const SetRecipeCommand& c = static_cast<const SetRecipeCommand&>(command);
out << "setrecipe " << c.id << ' ' << c.recipeId;
out << "setrecipe " << c.id.value() << ' ' << c.recipeId;
break;
}
case CommandKind::SetShipLayout:
{
const SetShipLayoutCommand& c = static_cast<const SetShipLayoutCommand&>(command);
out << "setlayout " << c.id << ' ';
out << "setlayout " << c.id.value() << ' ';
appendLayout(out, c.layout);
break;
}
@@ -154,7 +154,7 @@ std::string serializeCommand(const Command& command)
{
const SetSiteSplitterFiltersCommand& c =
static_cast<const SetSiteSplitterFiltersCommand&>(command);
out << "sitefilters " << c.id << ' ';
out << "sitefilters " << c.id.value() << ' ';
appendFilters(out, c.filterA, c.filterB);
break;
}
@@ -242,27 +242,35 @@ std::shared_ptr<Command> parseCommand(const std::string& tokens)
if (verb == "demolish")
{
std::shared_ptr<DemolishCommand> c = std::make_shared<DemolishCommand>();
if (!(in >> c->id)) { return nullptr; }
BuildingId id = 0;
if (!(in >> id)) { return nullptr; }
c->id = id;
return c;
}
if (verb == "rotate")
{
std::shared_ptr<RotateInPlaceCommand> c = std::make_shared<RotateInPlaceCommand>();
std::string rotToken;
if (!(in >> c->id >> rotToken)) { return nullptr; }
BuildingId id = 0;
if (!(in >> id >> rotToken)) { return nullptr; }
c->id = id;
c->newRotation = rotationFromString(rotToken);
return c;
}
if (verb == "setrecipe")
{
std::shared_ptr<SetRecipeCommand> c = std::make_shared<SetRecipeCommand>();
if (!(in >> c->id >> c->recipeId)) { return nullptr; }
BuildingId id = 0;
if (!(in >> id >> c->recipeId)) { return nullptr; }
c->id = id;
return c;
}
if (verb == "setlayout")
{
std::shared_ptr<SetShipLayoutCommand> c = std::make_shared<SetShipLayoutCommand>();
if (!(in >> c->id)) { return nullptr; }
BuildingId id = 0;
if (!(in >> id)) { return nullptr; }
c->id = id;
c->layout = parseLayout(in, ok);
if (!ok) { return nullptr; }
return c;
@@ -271,7 +279,9 @@ std::shared_ptr<Command> parseCommand(const std::string& tokens)
{
std::shared_ptr<SetSiteSplitterFiltersCommand> c =
std::make_shared<SetSiteSplitterFiltersCommand>();
if (!(in >> c->id)) { return nullptr; }
BuildingId id = 0;
if (!(in >> id)) { return nullptr; }
c->id = id;
parseFilters(in, c->filterA, c->filterB, ok);
if (!ok) { return nullptr; }
return c;

View File

@@ -40,7 +40,6 @@ Simulation::Simulation(GameConfig config, unsigned int seed)
, m_nextBuildingId(1)
, m_buildingBlocksStock(m_config.world.startingBuildingBlocks)
, m_gameOver(false)
, m_hqBuildingId(kInvalidBuildingId)
, m_hqProxyEntity(entt::null)
, m_playerStation1Entity(entt::null)
, m_playerStation2Entity(entt::null)
@@ -137,7 +136,7 @@ void Simulation::reset(unsigned int seed)
m_gameOver = false;
m_isWon = false;
m_artifactCount = 0;
m_hqBuildingId = kInvalidBuildingId;
m_hqBuildingId = std::nullopt;
m_hqProxyEntity = entt::null;
m_playerStation1Entity = entt::null;
m_playerStation2Entity = entt::null;
@@ -217,11 +216,12 @@ void Simulation::apply(const Command& command)
case CommandKind::PlaceBuilding:
{
const PlaceBuildingCommand& c = static_cast<const PlaceBuildingCommand&>(command);
const BuildingId id = tryPlaceBuilding(c.type, c.anchor, c.rotation);
if (id == kInvalidBuildingId)
const std::optional<BuildingId> placed = tryPlaceBuilding(c.type, c.anchor, c.rotation);
if (!placed.has_value())
{
break;
}
const BuildingId id = *placed;
if (c.recipeId.has_value())
{
m_buildingSystem->setRecipe(id, *c.recipeId);
@@ -237,31 +237,31 @@ void Simulation::apply(const Command& command)
break;
}
case CommandKind::Demolish:
demolish(static_cast<const DemolishCommand&>(command).id);
demolish(*static_cast<const DemolishCommand&>(command).id);
break;
case CommandKind::RotateInPlace:
{
const RotateInPlaceCommand& c = static_cast<const RotateInPlaceCommand&>(command);
m_buildingSystem->rotateInPlace(c.id, c.newRotation);
m_buildingSystem->rotateInPlace(*c.id, c.newRotation);
break;
}
case CommandKind::SetRecipe:
{
const SetRecipeCommand& c = static_cast<const SetRecipeCommand&>(command);
m_buildingSystem->setRecipe(c.id, c.recipeId);
m_buildingSystem->setRecipe(*c.id, c.recipeId);
break;
}
case CommandKind::SetShipLayout:
{
const SetShipLayoutCommand& c = static_cast<const SetShipLayoutCommand&>(command);
m_buildingSystem->setShipLayout(c.id, c.layout);
m_buildingSystem->setShipLayout(*c.id, c.layout);
break;
}
case CommandKind::SetSiteSplitterFilters:
{
const SetSiteSplitterFiltersCommand& c =
static_cast<const SetSiteSplitterFiltersCommand&>(command);
m_buildingSystem->setSiteSplitterFilters(c.id, c.filterA, c.filterB);
m_buildingSystem->setSiteSplitterFilters(*c.id, c.filterA, c.filterB);
break;
}
case CommandKind::SetSplitterFilters:
@@ -1193,11 +1193,11 @@ bool Simulation::isModuleSchematicUnlocked(const std::string& moduleId) const
return it->second.unlocked;
}
BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
std::optional<BuildingId> Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation)
{
if (!m_buildingSystem->isPlacementValid(type, anchor, rotation))
{
return kInvalidBuildingId;
return std::nullopt;
}
int cost = 0;
@@ -1211,7 +1211,7 @@ BuildingId Simulation::tryPlaceBuilding(BuildingType type, QPoint anchor, Rotati
}
if (m_buildingBlocksStock < cost)
{
return kInvalidBuildingId;
return std::nullopt;
}
m_buildingBlocksStock -= cost;
return m_buildingSystem->place(type, anchor, rotation, m_currentTick);

View File

@@ -2,6 +2,7 @@
#include <map>
#include <memory>
#include <optional>
#include <random>
#include <set>
#include <string>
@@ -131,8 +132,8 @@ private:
// Reached during play exclusively via apply(); never called by UI/app code.
// Checks affordability, deducts building blocks, and places the building.
// Returns the new entity id, or kInvalidBuildingId if blocks are insufficient.
BuildingId tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
// Returns the new entity id, or nullopt if blocks are insufficient.
std::optional<BuildingId> tryPlaceBuilding(BuildingType type, QPoint anchor, Rotation rotation);
// Demolishes the building with the given id and refunds building blocks.
void demolish(BuildingId id);
@@ -182,7 +183,7 @@ private:
int m_artifactCount = 0;
// Pre-placed structure IDs.
BuildingId m_hqBuildingId; // Building id (for belt integration)
std::optional<BuildingId> m_hqBuildingId; // Building id (for belt integration)
entt::entity m_hqProxyEntity; // ECS entity (HP, targeting)
entt::entity m_playerStation1Entity;
entt::entity m_playerStation2Entity;